expo-file-system 18.2.0-canary-20250709-136b77f → 18.2.0-canary-20250722-599a28f
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 +2 -0
- package/android/build.gradle +3 -2
- package/android/src/main/java/expo/modules/filesystem/next/FileSystemDirectory.kt +50 -13
- package/android/src/main/java/expo/modules/filesystem/next/FileSystemFile.kt +30 -47
- package/android/src/main/java/expo/modules/filesystem/next/FileSystemFileHandle.kt +1 -1
- package/android/src/main/java/expo/modules/filesystem/next/FileSystemNextModule.kt +19 -7
- package/android/src/main/java/expo/modules/filesystem/next/FileSystemNextRecords.kt +10 -0
- package/android/src/main/java/expo/modules/filesystem/next/FileSystemPath.kt +81 -28
- package/android/src/main/java/expo/modules/filesystem/next/unifiedfile/JavaFile.kt +34 -0
- package/android/src/main/java/expo/modules/filesystem/next/unifiedfile/SAFDocumentFile.kt +38 -0
- package/android/src/main/java/expo/modules/filesystem/next/unifiedfile/UnifiedFileInterface.kt +15 -0
- package/build/next/ExpoFileSystem.types.d.ts +34 -0
- package/build/next/ExpoFileSystem.types.d.ts.map +1 -1
- package/build/next/FileSystem.d.ts +2 -0
- 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 +4 -0
- 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-20250709-136b77f/expo.modules.filesystem-18.2.0-canary-20250709-136b77f-sources.jar → 18.2.0-canary-20250722-599a28f/expo.modules.filesystem-18.2.0-canary-20250722-599a28f-sources.jar} +0 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250722-599a28f/expo.modules.filesystem-18.2.0-canary-20250722-599a28f-sources.jar.md5 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250722-599a28f/expo.modules.filesystem-18.2.0-canary-20250722-599a28f-sources.jar.sha1 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250722-599a28f/expo.modules.filesystem-18.2.0-canary-20250722-599a28f-sources.jar.sha256 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250722-599a28f/expo.modules.filesystem-18.2.0-canary-20250722-599a28f-sources.jar.sha512 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250722-599a28f/expo.modules.filesystem-18.2.0-canary-20250722-599a28f.aar +0 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250722-599a28f/expo.modules.filesystem-18.2.0-canary-20250722-599a28f.aar.md5 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250722-599a28f/expo.modules.filesystem-18.2.0-canary-20250722-599a28f.aar.sha1 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250722-599a28f/expo.modules.filesystem-18.2.0-canary-20250722-599a28f.aar.sha256 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250722-599a28f/expo.modules.filesystem-18.2.0-canary-20250722-599a28f.aar.sha512 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/{18.2.0-canary-20250709-136b77f/expo.modules.filesystem-18.2.0-canary-20250709-136b77f.module → 18.2.0-canary-20250722-599a28f/expo.modules.filesystem-18.2.0-canary-20250722-599a28f.module} +36 -22
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250722-599a28f/expo.modules.filesystem-18.2.0-canary-20250722-599a28f.module.md5 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250722-599a28f/expo.modules.filesystem-18.2.0-canary-20250722-599a28f.module.sha1 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250722-599a28f/expo.modules.filesystem-18.2.0-canary-20250722-599a28f.module.sha256 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250722-599a28f/expo.modules.filesystem-18.2.0-canary-20250722-599a28f.module.sha512 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/{18.2.0-canary-20250709-136b77f/expo.modules.filesystem-18.2.0-canary-20250709-136b77f.pom → 18.2.0-canary-20250722-599a28f/expo.modules.filesystem-18.2.0-canary-20250722-599a28f.pom} +7 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250722-599a28f/expo.modules.filesystem-18.2.0-canary-20250722-599a28f.pom.md5 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250722-599a28f/expo.modules.filesystem-18.2.0-canary-20250722-599a28f.pom.sha1 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250722-599a28f/expo.modules.filesystem-18.2.0-canary-20250722-599a28f.pom.sha256 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250722-599a28f/expo.modules.filesystem-18.2.0-canary-20250722-599a28f.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 +38 -0
- package/src/next/FileSystem.ts +11 -2
- 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-20250709-136b77f/expo.modules.filesystem-18.2.0-canary-20250709-136b77f-sources.jar.md5 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250709-136b77f/expo.modules.filesystem-18.2.0-canary-20250709-136b77f-sources.jar.sha1 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250709-136b77f/expo.modules.filesystem-18.2.0-canary-20250709-136b77f-sources.jar.sha256 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250709-136b77f/expo.modules.filesystem-18.2.0-canary-20250709-136b77f-sources.jar.sha512 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250709-136b77f/expo.modules.filesystem-18.2.0-canary-20250709-136b77f.aar +0 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250709-136b77f/expo.modules.filesystem-18.2.0-canary-20250709-136b77f.aar.md5 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250709-136b77f/expo.modules.filesystem-18.2.0-canary-20250709-136b77f.aar.sha1 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250709-136b77f/expo.modules.filesystem-18.2.0-canary-20250709-136b77f.aar.sha256 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250709-136b77f/expo.modules.filesystem-18.2.0-canary-20250709-136b77f.aar.sha512 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250709-136b77f/expo.modules.filesystem-18.2.0-canary-20250709-136b77f.module.md5 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250709-136b77f/expo.modules.filesystem-18.2.0-canary-20250709-136b77f.module.sha1 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250709-136b77f/expo.modules.filesystem-18.2.0-canary-20250709-136b77f.module.sha256 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250709-136b77f/expo.modules.filesystem-18.2.0-canary-20250709-136b77f.module.sha512 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250709-136b77f/expo.modules.filesystem-18.2.0-canary-20250709-136b77f.pom.md5 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250709-136b77f/expo.modules.filesystem-18.2.0-canary-20250709-136b77f.pom.sha1 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250709-136b77f/expo.modules.filesystem-18.2.0-canary-20250709-136b77f.pom.sha256 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250709-136b77f/expo.modules.filesystem-18.2.0-canary-20250709-136b77f.pom.sha512 +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,9 +6,11 @@
|
|
|
6
6
|
|
|
7
7
|
### 🎉 New features
|
|
8
8
|
|
|
9
|
+
- Add directory info function ([#37910](https://github.com/expo/expo/pull/37910) by [@Wenszel](https://github.com/Wenszel))
|
|
9
10
|
- Add total and available sizes, directory sizes. ([#37594](https://github.com/expo/expo/pull/37594) by [@aleqsio](https://github.com/aleqsio))
|
|
10
11
|
- 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
12
|
- Add support for custom headers in downloadFileAsync ([#36108](https://github.com/expo/expo/pull/36108) by [@leonhh](https://github.com/leonhh))
|
|
13
|
+
- [next] Add limited support for SAF Uris. ([#38075](https://github.com/expo/expo/pull/38075) by [@aleqsio](https://github.com/aleqsio))
|
|
12
14
|
|
|
13
15
|
### 🐛 Bug fixes
|
|
14
16
|
|
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-20250722-599a28f'
|
|
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-20250722-599a28f"
|
|
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,7 +26,34 @@ class FileSystemDirectory(file: File) : FileSystemPath(file) {
|
|
|
26
26
|
val size: Long get() {
|
|
27
27
|
validatePermission(Permission.READ)
|
|
28
28
|
validateType()
|
|
29
|
-
return
|
|
29
|
+
return javaFile.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(javaFile.toURI().toString())
|
|
39
|
+
)
|
|
40
|
+
return directoryInfo
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
when {
|
|
44
|
+
javaFile.toURI().scheme == "file" -> {
|
|
45
|
+
val directoryInfo = DirectoryInfo(
|
|
46
|
+
exists = true,
|
|
47
|
+
uri = slashifyFilePath(javaFile.toURI().toString()),
|
|
48
|
+
files = javaFile.listFiles()?.map { i -> i.name },
|
|
49
|
+
modificationTime = modificationTime,
|
|
50
|
+
creationTime = creationTime,
|
|
51
|
+
size = size
|
|
52
|
+
)
|
|
53
|
+
return directoryInfo
|
|
54
|
+
}
|
|
55
|
+
else -> throw UnableToGetInfoException("file schema ${javaFile.toURI().scheme} is not supported")
|
|
56
|
+
}
|
|
30
57
|
}
|
|
31
58
|
|
|
32
59
|
fun create(options: CreateOptions = CreateOptions()) {
|
|
@@ -34,33 +61,43 @@ class FileSystemDirectory(file: File) : FileSystemPath(file) {
|
|
|
34
61
|
validatePermission(Permission.WRITE)
|
|
35
62
|
validateCanCreate(options)
|
|
36
63
|
if (options.overwrite && file.exists()) {
|
|
37
|
-
|
|
64
|
+
javaFile.delete()
|
|
38
65
|
}
|
|
39
66
|
val created = if (options.intermediates) {
|
|
40
|
-
|
|
67
|
+
javaFile.mkdirs()
|
|
41
68
|
} else {
|
|
42
|
-
|
|
69
|
+
javaFile.mkdir()
|
|
43
70
|
}
|
|
44
71
|
if (!created) {
|
|
45
72
|
throw UnableToCreateException("directory already exists or could not be created")
|
|
46
73
|
}
|
|
47
74
|
}
|
|
48
75
|
|
|
76
|
+
fun createFile(mimeType: String?, fileName: String): FileSystemFile {
|
|
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
|
+
val newDirectory = file.createDirectory(fileName) ?: throw UnableToCreateException("directory could not be created")
|
|
83
|
+
return FileSystemDirectory(newDirectory.uri)
|
|
84
|
+
}
|
|
85
|
+
|
|
49
86
|
// this function is internal and will be removed in the future (when returning arrays of shared objects is supported)
|
|
50
87
|
fun listAsRecords(): List<Map<String, Any>> {
|
|
51
88
|
validateType()
|
|
52
89
|
validatePermission(Permission.READ)
|
|
53
|
-
return file.
|
|
54
|
-
val uriString =
|
|
90
|
+
return file.listFilesAsUnified().map {
|
|
91
|
+
val uriString = it.uri.toString()
|
|
55
92
|
mapOf(
|
|
56
|
-
"isDirectory" to it.isDirectory,
|
|
93
|
+
"isDirectory" to it.isDirectory(),
|
|
57
94
|
"uri" to if (uriString.endsWith("/")) uriString else "$uriString/"
|
|
58
95
|
)
|
|
59
|
-
}
|
|
96
|
+
}
|
|
60
97
|
}
|
|
61
98
|
|
|
62
99
|
fun asString(): String {
|
|
63
|
-
val uriString =
|
|
100
|
+
val uriString = file.uri.toString()
|
|
64
101
|
return if (uriString.endsWith("/")) uriString else "$uriString/"
|
|
65
102
|
}
|
|
66
103
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
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
5
|
import android.webkit.MimeTypeMap
|
|
7
6
|
import expo.modules.filesystem.InfoOptions
|
|
@@ -9,16 +8,11 @@ import expo.modules.filesystem.slashifyFilePath
|
|
|
9
8
|
import expo.modules.interfaces.filesystem.Permission
|
|
10
9
|
import expo.modules.kotlin.apifeatures.EitherType
|
|
11
10
|
import expo.modules.kotlin.typedarray.TypedArray
|
|
12
|
-
import java.io.File
|
|
13
11
|
import java.io.FileOutputStream
|
|
14
|
-
import java.nio.file.attribute.BasicFileAttributes
|
|
15
12
|
import java.security.MessageDigest
|
|
16
|
-
import kotlin.io.path.Path
|
|
17
|
-
import kotlin.io.path.readAttributes
|
|
18
|
-
import kotlin.time.Duration.Companion.milliseconds
|
|
19
13
|
|
|
20
14
|
@OptIn(EitherType::class)
|
|
21
|
-
class FileSystemFile(
|
|
15
|
+
class FileSystemFile(uri: Uri) : FileSystemPath(uri) {
|
|
22
16
|
// 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
17
|
fun validatePath() {
|
|
24
18
|
}
|
|
@@ -27,14 +21,14 @@ class FileSystemFile(file: File) : FileSystemPath(file) {
|
|
|
27
21
|
// After calling this function, we can use the `isDirectory` and `isFile` functions safely as they will match the shared class used.
|
|
28
22
|
override fun validateType() {
|
|
29
23
|
validatePermission(Permission.READ)
|
|
30
|
-
if (file.exists() && file.isDirectory) {
|
|
24
|
+
if (file.exists() && file.isDirectory()) {
|
|
31
25
|
throw InvalidTypeFileException()
|
|
32
26
|
}
|
|
33
27
|
}
|
|
34
28
|
|
|
35
29
|
val exists: Boolean get() {
|
|
36
30
|
return if (checkPermission(Permission.READ)) {
|
|
37
|
-
file.isFile
|
|
31
|
+
file.isFile()
|
|
38
32
|
} else {
|
|
39
33
|
false
|
|
40
34
|
}
|
|
@@ -44,15 +38,19 @@ class FileSystemFile(file: File) : FileSystemPath(file) {
|
|
|
44
38
|
validateType()
|
|
45
39
|
validatePermission(Permission.WRITE)
|
|
46
40
|
validateCanCreate(options)
|
|
47
|
-
if (
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
41
|
+
if (uri.isContentUri) {
|
|
42
|
+
throw UnableToCreateException("create function does not work with SAF Uris, use `createDirectory` and `createFile` instead")
|
|
43
|
+
} else {
|
|
44
|
+
if (options.overwrite && exists) {
|
|
45
|
+
javaFile.delete()
|
|
46
|
+
}
|
|
47
|
+
if (options.intermediates) {
|
|
48
|
+
javaFile.parentFile?.mkdirs()
|
|
49
|
+
}
|
|
50
|
+
val created = javaFile.createNewFile()
|
|
51
|
+
if (!created) {
|
|
52
|
+
throw UnableToCreateException("file already exists or could not be created")
|
|
53
|
+
}
|
|
56
54
|
}
|
|
57
55
|
}
|
|
58
56
|
|
|
@@ -62,7 +60,7 @@ class FileSystemFile(file: File) : FileSystemPath(file) {
|
|
|
62
60
|
if (!exists) {
|
|
63
61
|
create()
|
|
64
62
|
}
|
|
65
|
-
FileOutputStream(
|
|
63
|
+
FileOutputStream(javaFile).use {
|
|
66
64
|
it.write(content.toByteArray())
|
|
67
65
|
}
|
|
68
66
|
}
|
|
@@ -73,85 +71,70 @@ class FileSystemFile(file: File) : FileSystemPath(file) {
|
|
|
73
71
|
if (!exists) {
|
|
74
72
|
create()
|
|
75
73
|
}
|
|
76
|
-
FileOutputStream(
|
|
74
|
+
FileOutputStream(javaFile).use {
|
|
77
75
|
it.channel.write(content.toDirectBuffer())
|
|
78
76
|
}
|
|
79
77
|
}
|
|
80
78
|
|
|
81
79
|
fun asString(): String {
|
|
82
|
-
val uriString =
|
|
80
|
+
val uriString = file.uri.toString()
|
|
83
81
|
return if (uriString.endsWith("/")) uriString.dropLast(1) else uriString
|
|
84
82
|
}
|
|
85
83
|
|
|
86
84
|
fun text(): String {
|
|
87
85
|
validateType()
|
|
88
86
|
validatePermission(Permission.READ)
|
|
89
|
-
return
|
|
87
|
+
return javaFile.readText()
|
|
90
88
|
}
|
|
91
89
|
|
|
92
90
|
fun base64(): String {
|
|
93
91
|
validateType()
|
|
94
92
|
validatePermission(Permission.READ)
|
|
95
|
-
return Base64.encodeToString(
|
|
93
|
+
return Base64.encodeToString(javaFile.readBytes(), Base64.NO_WRAP)
|
|
96
94
|
}
|
|
97
95
|
|
|
98
96
|
fun bytes(): ByteArray {
|
|
99
97
|
validateType()
|
|
100
98
|
validatePermission(Permission.READ)
|
|
101
|
-
return
|
|
99
|
+
return javaFile.readBytes()
|
|
102
100
|
}
|
|
103
101
|
|
|
104
102
|
@OptIn(ExperimentalStdlibApi::class)
|
|
105
103
|
val md5: String get() {
|
|
106
104
|
validatePermission(Permission.READ)
|
|
107
105
|
val md = MessageDigest.getInstance("MD5")
|
|
108
|
-
val digest = md.digest(
|
|
106
|
+
val digest = md.digest(javaFile.readBytes())
|
|
109
107
|
return digest.toHexString()
|
|
110
108
|
}
|
|
111
109
|
|
|
112
110
|
val size: Long? get() {
|
|
113
|
-
return if (
|
|
114
|
-
|
|
111
|
+
return if (javaFile.exists()) {
|
|
112
|
+
javaFile.length()
|
|
115
113
|
} else {
|
|
116
114
|
null
|
|
117
115
|
}
|
|
118
116
|
}
|
|
119
117
|
|
|
120
118
|
val type: String? get() {
|
|
121
|
-
return MimeTypeMap.getFileExtensionFromUrl(
|
|
119
|
+
return MimeTypeMap.getFileExtensionFromUrl(javaFile.path)
|
|
122
120
|
?.run { MimeTypeMap.getSingleton().getMimeTypeFromExtension(lowercase()) }
|
|
123
121
|
}
|
|
124
122
|
|
|
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
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
123
|
fun info(options: InfoOptions?): FileInfo {
|
|
141
124
|
validateType()
|
|
142
125
|
validatePermission(Permission.READ)
|
|
143
126
|
if (!file.exists()) {
|
|
144
127
|
val fileInfo = FileInfo(
|
|
145
128
|
exists = false,
|
|
146
|
-
uri = slashifyFilePath(
|
|
129
|
+
uri = slashifyFilePath(javaFile.toURI().toString())
|
|
147
130
|
)
|
|
148
131
|
return fileInfo
|
|
149
132
|
}
|
|
150
133
|
when {
|
|
151
|
-
|
|
134
|
+
javaFile.toURI().scheme == "file" -> {
|
|
152
135
|
val fileInfo = FileInfo(
|
|
153
136
|
exists = true,
|
|
154
|
-
uri = slashifyFilePath(
|
|
137
|
+
uri = slashifyFilePath(javaFile.toURI().toString()),
|
|
155
138
|
size = size,
|
|
156
139
|
modificationTime = modificationTime,
|
|
157
140
|
creationTime = creationTime
|
|
@@ -161,7 +144,7 @@ class FileSystemFile(file: File) : FileSystemPath(file) {
|
|
|
161
144
|
}
|
|
162
145
|
return fileInfo
|
|
163
146
|
}
|
|
164
|
-
else -> throw UnableToGetInfoException("file schema ${
|
|
147
|
+
else -> throw UnableToGetInfoException("file schema ${javaFile.toURI().scheme} is not supported")
|
|
165
148
|
}
|
|
166
149
|
}
|
|
167
150
|
}
|
|
@@ -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 ->
|
|
@@ -210,8 +210,12 @@ class FileSystemNextModule : Module() {
|
|
|
210
210
|
}
|
|
211
211
|
|
|
212
212
|
Class(FileSystemDirectory::class) {
|
|
213
|
-
Constructor { uri:
|
|
214
|
-
FileSystemDirectory(
|
|
213
|
+
Constructor { uri: Uri ->
|
|
214
|
+
FileSystemDirectory(uri)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
Function("info") { directory: FileSystemDirectory ->
|
|
218
|
+
directory.info()
|
|
215
219
|
}
|
|
216
220
|
|
|
217
221
|
Function("delete") { directory: FileSystemDirectory ->
|
|
@@ -222,6 +226,14 @@ class FileSystemNextModule : Module() {
|
|
|
222
226
|
directory.create(options ?: CreateOptions())
|
|
223
227
|
}
|
|
224
228
|
|
|
229
|
+
Function("createDirectory") { file: FileSystemDirectory, name: String ->
|
|
230
|
+
return@Function file.createDirectory(name)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
Function("createFile") { file: FileSystemDirectory, name: String, mimeType: String? ->
|
|
234
|
+
return@Function file.createFile(mimeType, name)
|
|
235
|
+
}
|
|
236
|
+
|
|
225
237
|
Property("exists") { directory: FileSystemDirectory ->
|
|
226
238
|
directory.exists
|
|
227
239
|
}
|
|
@@ -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,69 @@
|
|
|
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
|
|
12
|
+
import java.nio.file.attribute.BasicFileAttributes
|
|
7
13
|
import java.util.EnumSet
|
|
14
|
+
import kotlin.io.path.Path
|
|
8
15
|
import kotlin.io.path.moveTo
|
|
16
|
+
import kotlin.io.path.readAttributes
|
|
17
|
+
import kotlin.time.Duration.Companion.milliseconds
|
|
9
18
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
abstract class FileSystemPath(
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
val Uri.isContentUri get(): Boolean {
|
|
20
|
+
return scheme == "content"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
abstract class FileSystemPath(var uri: Uri) : SharedObject() {
|
|
24
|
+
private var fileAdapter: UnifiedFileInterface? = null
|
|
25
|
+
val file: UnifiedFileInterface get() {
|
|
26
|
+
val currentAdapter = fileAdapter
|
|
27
|
+
if (currentAdapter?.uri == uri) {
|
|
28
|
+
return currentAdapter
|
|
29
|
+
}
|
|
30
|
+
val newAdapter = if (uri.isContentUri) {
|
|
31
|
+
SAFDocumentFile(appContext?.reactContext ?: throw Exception("No context"), uri)
|
|
32
|
+
} else {
|
|
33
|
+
JavaFile(uri)
|
|
34
|
+
}
|
|
35
|
+
fileAdapter = newAdapter
|
|
36
|
+
return newAdapter
|
|
37
|
+
}
|
|
38
|
+
val javaFile: File get() =
|
|
39
|
+
if (uri.isContentUri) {
|
|
40
|
+
throw Exception("This method cannot be used with content URIs: $uri")
|
|
41
|
+
} else {
|
|
42
|
+
(file as File)
|
|
18
43
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
44
|
+
|
|
45
|
+
fun delete() {
|
|
46
|
+
if (!file.exists()) {
|
|
47
|
+
throw UnableToDeleteException("uri '${file.uri}' does not exist")
|
|
48
|
+
}
|
|
49
|
+
if (file.isDirectory()) {
|
|
50
|
+
file.listFilesAsUnified().forEach { child ->
|
|
51
|
+
if (child.isDirectory()) {
|
|
22
52
|
// Recursively delete subdirectories
|
|
23
|
-
|
|
53
|
+
if (uri.isContentUri) {
|
|
54
|
+
SAFDocumentFile(appContext?.reactContext ?: throw Exception("No context"), child.uri).delete()
|
|
55
|
+
} else {
|
|
56
|
+
JavaFile(child.uri).delete()
|
|
57
|
+
}
|
|
24
58
|
} else {
|
|
25
59
|
if (!child.delete()) {
|
|
26
|
-
throw UnableToDeleteException("failed to delete '${child.
|
|
60
|
+
throw UnableToDeleteException("failed to delete '${child.uri}'")
|
|
27
61
|
}
|
|
28
62
|
}
|
|
29
63
|
}
|
|
30
64
|
}
|
|
31
|
-
if (!
|
|
32
|
-
throw UnableToDeleteException("failed to delete '${
|
|
65
|
+
if (!file.delete()) {
|
|
66
|
+
throw UnableToDeleteException("failed to delete '${file.uri}'")
|
|
33
67
|
}
|
|
34
68
|
}
|
|
35
69
|
|
|
@@ -41,26 +75,26 @@ abstract class FileSystemPath(public var file: File) : SharedObject() {
|
|
|
41
75
|
if (!destination.exists) {
|
|
42
76
|
throw DestinationDoesNotExistException()
|
|
43
77
|
}
|
|
44
|
-
return File(destination.
|
|
78
|
+
return File(destination.javaFile, javaFile.name)
|
|
45
79
|
}
|
|
46
80
|
// this if FileSystemDirectory
|
|
47
81
|
// we match unix behavior https://askubuntu.com/a/763915
|
|
48
82
|
if (destination.exists) {
|
|
49
|
-
return File(destination.
|
|
83
|
+
return File(destination.javaFile, javaFile.name)
|
|
50
84
|
}
|
|
51
|
-
if (destination.
|
|
85
|
+
if (destination.javaFile.parentFile?.exists() != true) {
|
|
52
86
|
throw DestinationDoesNotExistException()
|
|
53
87
|
}
|
|
54
|
-
return destination.
|
|
88
|
+
return destination.javaFile
|
|
55
89
|
}
|
|
56
90
|
// destination is FileSystemFile
|
|
57
91
|
if (this !is FileSystemFile) {
|
|
58
92
|
throw CopyOrMoveDirectoryToFileException()
|
|
59
93
|
}
|
|
60
|
-
if (destination.
|
|
94
|
+
if (destination.javaFile.parentFile?.exists() != true) {
|
|
61
95
|
throw DestinationDoesNotExistException()
|
|
62
96
|
}
|
|
63
|
-
return destination.
|
|
97
|
+
return destination.javaFile
|
|
64
98
|
}
|
|
65
99
|
|
|
66
100
|
fun validatePermission(permission: Permission) {
|
|
@@ -70,7 +104,11 @@ abstract class FileSystemPath(public var file: File) : SharedObject() {
|
|
|
70
104
|
}
|
|
71
105
|
|
|
72
106
|
fun checkPermission(permission: Permission): Boolean {
|
|
73
|
-
|
|
107
|
+
if (uri.isContentUri) {
|
|
108
|
+
// TODO: Consider adding a check for content URIs (not in legacy FS)
|
|
109
|
+
return true
|
|
110
|
+
}
|
|
111
|
+
val permissions = appContext?.filePermission?.getPathPermissions(appContext?.reactContext, javaFile.path) ?: EnumSet.noneOf(Permission::class.java)
|
|
74
112
|
return permissions.contains(permission)
|
|
75
113
|
}
|
|
76
114
|
|
|
@@ -86,7 +124,7 @@ abstract class FileSystemPath(public var file: File) : SharedObject() {
|
|
|
86
124
|
validatePermission(Permission.READ)
|
|
87
125
|
to.validatePermission(Permission.WRITE)
|
|
88
126
|
|
|
89
|
-
|
|
127
|
+
javaFile.copyRecursively(getMoveOrCopyPath(to))
|
|
90
128
|
}
|
|
91
129
|
|
|
92
130
|
fun move(to: FileSystemPath) {
|
|
@@ -97,12 +135,27 @@ abstract class FileSystemPath(public var file: File) : SharedObject() {
|
|
|
97
135
|
|
|
98
136
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
99
137
|
val destination = getMoveOrCopyPath(to)
|
|
100
|
-
|
|
101
|
-
|
|
138
|
+
javaFile.toPath().moveTo(destination.toPath())
|
|
139
|
+
uri = destination.toUri()
|
|
140
|
+
} else {
|
|
141
|
+
javaFile.copyTo(getMoveOrCopyPath(to))
|
|
142
|
+
javaFile.delete()
|
|
143
|
+
uri = getMoveOrCopyPath(to).toUri()
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
val modificationTime: Long get() {
|
|
148
|
+
validateType()
|
|
149
|
+
return javaFile.lastModified()
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
val creationTime: Long? get() {
|
|
153
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
154
|
+
validateType()
|
|
155
|
+
val attributes = Path(javaFile.path).readAttributes<BasicFileAttributes>()
|
|
156
|
+
return attributes.creationTime().toMillis().milliseconds.inWholeMilliseconds
|
|
102
157
|
} else {
|
|
103
|
-
|
|
104
|
-
file.delete()
|
|
105
|
-
file = getMoveOrCopyPath(to)
|
|
158
|
+
return null
|
|
106
159
|
}
|
|
107
160
|
}
|
|
108
161
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
package expo.modules.filesystem.next.unifiedfile
|
|
2
|
+
|
|
3
|
+
import android.net.Uri
|
|
4
|
+
import androidx.core.net.toUri
|
|
5
|
+
import java.io.File
|
|
6
|
+
import java.net.URI
|
|
7
|
+
|
|
8
|
+
class JavaFile(override val uri: Uri) : UnifiedFileInterface, File(URI.create(uri.toString())) {
|
|
9
|
+
override fun isDirectory(): Boolean {
|
|
10
|
+
return super<File>.isDirectory()
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
override fun isFile(): Boolean {
|
|
14
|
+
return super<File>.isFile()
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
override val parentFile: UnifiedFileInterface?
|
|
18
|
+
get() = super<File>.parentFile?.toUri()?.let { JavaFile(it) }
|
|
19
|
+
|
|
20
|
+
override fun createFile(mimeType: String, displayName: String): UnifiedFileInterface? {
|
|
21
|
+
val childFile = File(super<File>.parentFile, displayName)
|
|
22
|
+
childFile.createNewFile()
|
|
23
|
+
return JavaFile(childFile.toUri())
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
override fun createDirectory(displayName: String): UnifiedFileInterface? {
|
|
27
|
+
val childFile = File(super<File>.parentFile, displayName)
|
|
28
|
+
childFile.mkdir()
|
|
29
|
+
return JavaFile(childFile.toUri())
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
override fun listFilesAsUnified(): List<UnifiedFileInterface> =
|
|
33
|
+
super<File>.listFiles()?.map { JavaFile(it.toUri()) } ?: emptyList()
|
|
34
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
package expo.modules.filesystem.next.unifiedfile
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.net.Uri
|
|
5
|
+
import androidx.documentfile.provider.DocumentFile
|
|
6
|
+
|
|
7
|
+
class SAFDocumentFile(private val context: Context, override val uri: Uri) : UnifiedFileInterface {
|
|
8
|
+
private val treeDocumentFile: DocumentFile? = DocumentFile.fromTreeUri(context, uri)
|
|
9
|
+
private val singleDocumentFile: DocumentFile? = DocumentFile.fromSingleUri(context, uri)
|
|
10
|
+
|
|
11
|
+
override fun exists(): Boolean = singleDocumentFile?.exists() == true
|
|
12
|
+
|
|
13
|
+
override fun isDirectory(): Boolean {
|
|
14
|
+
return singleDocumentFile?.isDirectory == true
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
override fun isFile(): Boolean {
|
|
18
|
+
return singleDocumentFile?.isFile == true
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
override val parentFile: UnifiedFileInterface?
|
|
22
|
+
get() = treeDocumentFile?.parentFile?.uri?.let { SAFDocumentFile(context, it) }
|
|
23
|
+
|
|
24
|
+
override fun createFile(mimeType: String, displayName: String): UnifiedFileInterface? {
|
|
25
|
+
val documentFile = treeDocumentFile?.createFile(mimeType, displayName)
|
|
26
|
+
return documentFile?.uri?.let { SAFDocumentFile(context, it) }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
override fun createDirectory(displayName: String): UnifiedFileInterface? {
|
|
30
|
+
val documentFile = treeDocumentFile?.createDirectory(displayName)
|
|
31
|
+
return documentFile?.uri?.let { SAFDocumentFile(context, it) }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
override fun delete(): Boolean = singleDocumentFile?.delete() ?: false
|
|
35
|
+
|
|
36
|
+
override fun listFilesAsUnified(): List<UnifiedFileInterface> =
|
|
37
|
+
treeDocumentFile?.listFiles()?.map { SAFDocumentFile(context, it.uri) } ?: emptyList()
|
|
38
|
+
}
|