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.
Files changed (74) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/android/build.gradle +3 -2
  3. package/android/src/main/java/expo/modules/filesystem/next/FileSystemDirectory.kt +51 -12
  4. package/android/src/main/java/expo/modules/filesystem/next/FileSystemFile.kt +48 -57
  5. package/android/src/main/java/expo/modules/filesystem/next/FileSystemFileHandle.kt +1 -1
  6. package/android/src/main/java/expo/modules/filesystem/next/FileSystemNextModule.kt +34 -10
  7. package/android/src/main/java/expo/modules/filesystem/next/FileSystemNextRecords.kt +10 -0
  8. package/android/src/main/java/expo/modules/filesystem/next/FileSystemPath.kt +71 -28
  9. package/android/src/main/java/expo/modules/filesystem/next/unifiedfile/JavaFile.kt +65 -0
  10. package/android/src/main/java/expo/modules/filesystem/next/unifiedfile/SAFDocumentFile.kt +79 -0
  11. package/android/src/main/java/expo/modules/filesystem/next/unifiedfile/UnifiedFileInterface.kt +23 -0
  12. package/build/next/ExpoFileSystem.types.d.ts +57 -8
  13. package/build/next/ExpoFileSystem.types.d.ts.map +1 -1
  14. package/build/next/FileSystem.d.ts +6 -24
  15. package/build/next/FileSystem.d.ts.map +1 -1
  16. package/build/next/index.d.ts +1 -1
  17. package/build/next/index.d.ts.map +1 -1
  18. package/build/next/pathUtilities/index.d.ts.map +1 -1
  19. package/build/next/pathUtilities/url.d.ts +3 -4
  20. package/build/next/pathUtilities/url.d.ts.map +1 -1
  21. package/expo-module.config.json +1 -1
  22. package/ios/Next/FileSystemDirectory.swift +24 -0
  23. package/ios/Next/FileSystemFile.swift +0 -27
  24. package/ios/Next/FileSystemNextModule.swift +19 -3
  25. package/ios/Next/FileSystemNextRecords.swift +9 -0
  26. package/ios/Next/FileSystemPath.swift +27 -0
  27. 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
  28. 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
  29. 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
  30. 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
  31. 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
  32. 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
  33. 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
  34. 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
  35. 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
  36. 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
  37. 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
  38. 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
  39. 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
  40. 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
  41. 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
  42. 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
  43. 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
  44. 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
  45. 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
  46. 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
  47. package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/maven-metadata.xml +4 -4
  48. package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/maven-metadata.xml.md5 +1 -1
  49. package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/maven-metadata.xml.sha1 +1 -1
  50. package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/maven-metadata.xml.sha256 +1 -1
  51. package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/maven-metadata.xml.sha512 +1 -1
  52. package/package.json +4 -4
  53. package/src/next/ExpoFileSystem.types.ts +64 -8
  54. package/src/next/FileSystem.ts +25 -62
  55. package/src/next/index.ts +1 -0
  56. package/src/next/pathUtilities/index.ts +29 -21
  57. package/src/next/pathUtilities/url.ts +17 -32
  58. 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
  59. 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
  60. 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
  61. 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
  62. 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
  63. 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
  64. 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
  65. 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
  66. 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
  67. 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
  68. 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
  69. 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
  70. 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
  71. 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
  72. 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
  73. 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
  74. 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
 
@@ -4,13 +4,13 @@ plugins {
4
4
  }
5
5
 
6
6
  group = 'host.exp.exponent'
7
- version = '18.2.0-canary-20250713-8f814f8'
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-20250713-8f814f8"
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(file: File) : FileSystemPath(file) {
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
- file.mkdirs()
65
+ javaFile.mkdirs()
41
66
  } else {
42
- file.mkdir()
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.listFiles()?.map {
54
- val uriString = Uri.fromFile(it).toString()
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
- } ?: emptyList()
98
+ }
60
99
  }
61
100
 
62
101
  fun asString(): String {
63
- val uriString = Uri.fromFile(file).toString()
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(file: File) : FileSystemPath(file) {
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 (options.overwrite && file.exists()) {
48
- file.delete()
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
- file.parentFile?.mkdirs()
47
+ javaFile.parentFile?.mkdirs()
52
48
  }
53
- val created = file.createNewFile()
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
- FileOutputStream(file).use {
66
- it.write(content.toByteArray())
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
- FileOutputStream(file).use {
77
- it.channel.write(content.toDirectBuffer())
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 = Uri.fromFile(file).toString()
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.readText()
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
- return Base64.encodeToString(file.readBytes(), Base64.NO_WRAP)
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
- return file.readBytes()
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
- val digest = md.digest(file.readBytes())
109
- return digest.toHexString()
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 MimeTypeMap.getFileExtensionFromUrl(file.path)
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.toURI().toString())
142
+ uri = slashifyFilePath(file.uri.toString())
147
143
  )
148
144
  return fileInfo
149
145
  }
150
- when {
151
- file.toURI().scheme == "file" -> {
152
- val fileInfo = FileInfo(
153
- exists = true,
154
- uri = slashifyFilePath(file.toURI().toString()),
155
- size = size,
156
- modificationTime = modificationTime,
157
- creationTime = creationTime
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.file, "rw").channel), AutoCloseable {
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.file, fileName)
66
+ File(to.javaFile, fileName)
67
67
  } else {
68
- to.file
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.path
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: URI ->
97
- FileSystemFile(File(uri.path))
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
- Function("text") { file: FileSystemFile ->
124
+ AsyncFunction("text") { file: FileSystemFile ->
125
125
  file.text()
126
126
  }
127
127
 
128
- Function("base64") { file: FileSystemFile ->
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
- Function("bytes") { file: FileSystemFile ->
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: URI ->
214
- FileSystemDirectory(File(uri.path))
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
- // We use the `File` class to represent a file or a directory in the file system.
11
- // The Path class might be better, but `java.nio.file.Path` class is not available in API 23.
12
- // The URL, URI classes seem like a less suitable choice.
13
- // https://stackoverflow.com/questions/27845223/whats-the-difference-between-a-resource-uri-url-path-and-file-in-java
14
- abstract class FileSystemPath(public var file: File) : SharedObject() {
15
- fun delete(fileOrDirectory: File = file) {
16
- if (!fileOrDirectory.exists()) {
17
- throw UnableToDeleteException("path '${fileOrDirectory.path}' does not exist")
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 (fileOrDirectory.isDirectory) {
20
- fileOrDirectory.listFiles()?.forEach { child ->
21
- if (child.isDirectory) {
45
+ if (file.isDirectory()) {
46
+ file.listFilesAsUnified().forEach { child ->
47
+ if (child.isDirectory()) {
22
48
  // Recursively delete subdirectories
23
- delete(child)
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.path}'")
56
+ throw UnableToDeleteException("failed to delete '${child.uri}'")
27
57
  }
28
58
  }
29
59
  }
30
60
  }
31
- if (!fileOrDirectory.delete()) {
32
- throw UnableToDeleteException("failed to delete '${fileOrDirectory.path}'")
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.file, file.name)
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.file, file.name)
79
+ return File(destination.javaFile, javaFile.name)
50
80
  }
51
- if (destination.file.parentFile?.exists() != true) {
81
+ if (destination.javaFile.parentFile?.exists() != true) {
52
82
  throw DestinationDoesNotExistException()
53
83
  }
54
- return destination.file
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.file.parentFile?.exists() != true) {
90
+ if (destination.javaFile.parentFile?.exists() != true) {
61
91
  throw DestinationDoesNotExistException()
62
92
  }
63
- return destination.file
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
- val permissions = appContext?.filePermission?.getPathPermissions(appContext?.reactContext, file.path) ?: EnumSet.noneOf(Permission::class.java)
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
- file.copyRecursively(getMoveOrCopyPath(to))
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
- file.toPath().moveTo(destination.toPath())
101
- file = destination
134
+ javaFile.toPath().moveTo(destination.toPath())
135
+ uri = destination.toUri()
102
136
  } else {
103
- file.copyTo(getMoveOrCopyPath(to))
104
- file.delete()
105
- file = getMoveOrCopyPath(to)
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
  }