expo-file-system 18.0.2 → 18.0.3

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 CHANGED
@@ -10,6 +10,12 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 18.0.3 — 2024-11-13
14
+
15
+ ### 🎉 New features
16
+
17
+ - [next] Add file handles. ([#31738](https://github.com/expo/expo/pull/31738) by [@aleqsio](https://github.com/aleqsio))
18
+
13
19
  ## 18.0.2 — 2024-11-11
14
20
 
15
21
  _This version does not introduce any user-facing changes._
@@ -1,7 +1,7 @@
1
1
  apply plugin: 'com.android.library'
2
2
 
3
3
  group = 'host.exp.exponent'
4
- version = '18.0.2'
4
+ version = '18.0.3'
5
5
 
6
6
  def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
7
7
  apply from: expoModulesCorePlugin
@@ -14,7 +14,7 @@ android {
14
14
  namespace "expo.modules.filesystem"
15
15
  defaultConfig {
16
16
  versionCode 30
17
- versionName "18.0.2"
17
+ versionName "18.0.3"
18
18
  }
19
19
  }
20
20
 
@@ -0,0 +1,68 @@
1
+ package expo.modules.filesystem.next
2
+
3
+ import expo.modules.kotlin.sharedobjects.SharedRef
4
+ import java.io.RandomAccessFile
5
+ import java.nio.ByteBuffer
6
+ import java.nio.channels.FileChannel
7
+ class FileSystemFileHandle(file: FileSystemFile) : SharedRef<FileChannel>(RandomAccessFile(file.file, "rw").channel), AutoCloseable {
8
+ private val fileChannel: FileChannel = ref
9
+
10
+ private fun ensureIsOpen() {
11
+ if (!fileChannel.isOpen) {
12
+ throw UnableToReadHandleException("file handle is closed")
13
+ }
14
+ }
15
+
16
+ override fun sharedObjectDidRelease() {
17
+ close()
18
+ }
19
+
20
+ override fun close() {
21
+ fileChannel.close()
22
+ }
23
+
24
+ fun read(length: Int): ByteArray {
25
+ ensureIsOpen()
26
+ try {
27
+ val buffer = ByteBuffer.allocate(length.coerceAtMost((fileChannel.size() - fileChannel.position()).toInt()))
28
+ fileChannel.read(buffer)
29
+ return buffer.array()
30
+ } catch (e: Exception) {
31
+ throw UnableToReadHandleException(e.message ?: "unknown error")
32
+ }
33
+ }
34
+
35
+ fun write(data: ByteArray) {
36
+ ensureIsOpen()
37
+ try {
38
+ val buffer = ByteBuffer.wrap(data)
39
+ fileChannel.write(buffer)
40
+ } catch (e: Exception) {
41
+ throw UnableToWriteHandleException(e.message ?: "unknown error")
42
+ }
43
+ }
44
+
45
+ var offset: Long?
46
+ get() {
47
+ return try {
48
+ fileChannel.position()
49
+ } catch (e: Exception) {
50
+ null
51
+ }
52
+ }
53
+ set(value) {
54
+ if (value == null) {
55
+ return
56
+ }
57
+ fileChannel.position(value)
58
+ }
59
+
60
+ val size: Long?
61
+ get() {
62
+ return try {
63
+ fileChannel.size()
64
+ } catch (e: Exception) {
65
+ null
66
+ }
67
+ }
68
+ }
@@ -29,6 +29,16 @@ internal class InvalidPermissionException(permission: Permission) :
29
29
  "Missing '${permission.name}' permission for accessing the file."
30
30
  )
31
31
 
32
+ internal class UnableToReadHandleException(reason: String) :
33
+ CodedException(
34
+ "Unable to read from a file handle: '$reason'"
35
+ )
36
+
37
+ internal class UnableToWriteHandleException(reason: String) :
38
+ CodedException(
39
+ "Unable to write to a file handle: '$reason'"
40
+ )
41
+
32
42
  internal class DestinationAlreadyExistsException :
33
43
  CodedException(
34
44
  "Destination already exists"
@@ -133,6 +133,33 @@ class FileSystemNextModule : Module() {
133
133
  null
134
134
  }
135
135
  }
136
+
137
+ Function("open") { file: FileSystemFile ->
138
+ FileSystemFileHandle(file)
139
+ }
140
+ }
141
+
142
+ Class(FileSystemFileHandle::class) {
143
+ Constructor { file: FileSystemFile ->
144
+ FileSystemFileHandle(file)
145
+ }
146
+ Function("readBytes") { fileHandle: FileSystemFileHandle, bytes: Int ->
147
+ fileHandle.read(bytes)
148
+ }
149
+ Function("writeBytes") { fileHandle: FileSystemFileHandle, data: ByteArray ->
150
+ fileHandle.write(data)
151
+ }
152
+ Function("close") { fileHandle: FileSystemFileHandle ->
153
+ fileHandle.close()
154
+ }
155
+ Property("offset") { fileHandle: FileSystemFileHandle ->
156
+ fileHandle.offset
157
+ }.set { fileHandle: FileSystemFileHandle, offset: Long ->
158
+ fileHandle.offset = offset
159
+ }
160
+ Property("size") { fileHandle: FileSystemFileHandle ->
161
+ fileHandle.size
162
+ }
136
163
  }
137
164
 
138
165
  Class(FileSystemDirectory::class) {
@@ -11,7 +11,7 @@ import kotlin.io.path.moveTo
11
11
  // The Path class might be better, but `java.nio.file.Path` class is not available in API 23.
12
12
  // The URL, URI classes seem like a less suitable choice.
13
13
  // https://stackoverflow.com/questions/27845223/whats-the-difference-between-a-resource-uri-url-path-and-file-in-java
14
- abstract class FileSystemPath(var file: File) : SharedObject() {
14
+ abstract class FileSystemPath(public var file: File) : SharedObject() {
15
15
  fun delete() {
16
16
  if (!file.exists()) {
17
17
  throw UnableToDeleteException("path does not exist")
@@ -115,6 +115,11 @@ export declare class File {
115
115
  * Moves a directory. Updates the `uri` property that now points to the new location.
116
116
  */
117
117
  move(destination: Directory | File): any;
118
+ /**
119
+ * Returns a FileHandle object that can be used to read and write data to the file.
120
+ * @throws Error if the file does not exist or cannot be opened.
121
+ */
122
+ open(): FileHandle;
118
123
  /**
119
124
  * A static method that downloads a file from the network.
120
125
  * @param url - The URL of the file to download.
@@ -135,4 +140,11 @@ export declare class File {
135
140
  */
136
141
  md5: string | null;
137
142
  }
143
+ export declare class FileHandle {
144
+ close(): void;
145
+ readBytes(length: number): Uint8Array;
146
+ writeBytes(bytes: Uint8Array): void;
147
+ offset: number | null;
148
+ size: number | null;
149
+ }
138
150
  //# sourceMappingURL=ExpoFileSystem.types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoFileSystem.types.d.ts","sourceRoot":"","sources":["../../src/next/ExpoFileSystem.types.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,OAAO,OAAO,SAAS;IAC5B;;;;;;;OAOG;gBACS,GAAG,IAAI,EAAE,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE;IAElD;;OAEG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,YAAY,IAAI,IAAI;IAEpB;;;;OAIG;IACH,MAAM,IAAI,IAAI;IAEd;;;OAGG;IACH,MAAM,EAAE,OAAO,CAAC;IAEhB;;;;OAIG;IACH,MAAM,IAAI,IAAI;IAEd;;OAEG;IACH,IAAI,CAAC,WAAW,EAAE,SAAS,GAAG,IAAI;IAElC;;OAEG;IACH,IAAI,CAAC,WAAW,EAAE,SAAS,GAAG,IAAI;IAElC;;;;OAIG;IACH,aAAa,IAAI;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE;IAExD;;OAEG;IACH,IAAI,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,EAAE;CAC7B;AAED;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,IAAI;IACvB;;;;OAIG;gBACS,GAAG,IAAI,EAAE,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE;IAElD;;OAEG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,YAAY,IAAI,IAAI;IAEpB;;;OAGG;IACH,IAAI,IAAI,MAAM;IAEd;;;OAGG;IACH,MAAM,IAAI,MAAM;IAEhB;;;OAGG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAE5B;;;;OAIG;IACH,MAAM,IAAI,IAAI;IAEd;;;OAGG;IACH,MAAM,EAAE,OAAO,CAAC;IAEhB;;;;OAIG;IACH,MAAM,IAAI,IAAI;IAEd;;OAEG;IACH,IAAI,CAAC,WAAW,EAAE,SAAS,GAAG,IAAI;IAElC;;OAEG;IACH,IAAI,CAAC,WAAW,EAAE,SAAS,GAAG,IAAI;IAElC;;;;;;;;;OASG;IACH,MAAM,CAAC,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,SAAS,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAEnF;;OAEG;IACH,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAEpB;;OAEG;IACH,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CACpB"}
1
+ {"version":3,"file":"ExpoFileSystem.types.d.ts","sourceRoot":"","sources":["../../src/next/ExpoFileSystem.types.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,OAAO,OAAO,SAAS;IAC5B;;;;;;;OAOG;gBACS,GAAG,IAAI,EAAE,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE;IAElD;;OAEG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,YAAY,IAAI,IAAI;IAEpB;;;;OAIG;IACH,MAAM,IAAI,IAAI;IAEd;;;OAGG;IACH,MAAM,EAAE,OAAO,CAAC;IAEhB;;;;OAIG;IACH,MAAM,IAAI,IAAI;IAEd;;OAEG;IACH,IAAI,CAAC,WAAW,EAAE,SAAS,GAAG,IAAI;IAElC;;OAEG;IACH,IAAI,CAAC,WAAW,EAAE,SAAS,GAAG,IAAI;IAElC;;;;OAIG;IACH,aAAa,IAAI;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE;IAExD;;OAEG;IACH,IAAI,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,EAAE;CAC7B;AAED;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,IAAI;IACvB;;;;OAIG;gBACS,GAAG,IAAI,EAAE,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE;IAElD;;OAEG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,YAAY,IAAI,IAAI;IAEpB;;;OAGG;IACH,IAAI,IAAI,MAAM;IAEd;;;OAGG;IACH,MAAM,IAAI,MAAM;IAEhB;;;OAGG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAE5B;;;;OAIG;IACH,MAAM,IAAI,IAAI;IAEd;;;OAGG;IACH,MAAM,EAAE,OAAO,CAAC;IAEhB;;;;OAIG;IACH,MAAM,IAAI,IAAI;IAEd;;OAEG;IACH,IAAI,CAAC,WAAW,EAAE,SAAS,GAAG,IAAI;IAElC;;OAEG;IACH,IAAI,CAAC,WAAW,EAAE,SAAS,GAAG,IAAI;IAElC;;;OAGG;IACH,IAAI,IAAI,UAAU;IAElB;;;;;;;;;OASG;IACH,MAAM,CAAC,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,SAAS,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAEnF;;OAEG;IACH,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAEpB;;OAEG;IACH,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CACpB;AAED,MAAM,CAAC,OAAO,OAAO,UAAU;IAI7B,KAAK,IAAI,IAAI;IAKb,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU;IAKrC,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAMnC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAItB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB"}
@@ -1,3 +1,4 @@
1
+ import { ReadableStream, WritableStream } from 'web-streams-polyfill';
1
2
  import ExpoFileSystem from './ExpoFileSystem';
2
3
  import { PathUtilities } from './pathUtilities';
3
4
  export declare class Paths extends PathUtilities {
@@ -31,6 +32,8 @@ export declare class File extends ExpoFileSystem.FileSystemFile {
31
32
  * File name. Includes the extension.
32
33
  */
33
34
  get name(): string;
35
+ readableStream(): ReadableStream<Uint8Array>;
36
+ writableStream(): WritableStream<Uint8Array>;
34
37
  }
35
38
  /**
36
39
  * Represents a directory on the filesystem.
@@ -1 +1 @@
1
- {"version":3,"file":"FileSystem.d.ts","sourceRoot":"","sources":["../../src/next/FileSystem.ts"],"names":[],"mappings":"AAAA,OAAO,cAAc,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD,qBAAa,KAAM,SAAQ,aAAa;IACtC;;OAEG;IACH,MAAM,KAAK,KAAK,cAEf;IAED;;OAEG;IACH,MAAM,KAAK,QAAQ,cAElB;IACD,MAAM,KAAK,qBAAqB,8BAO/B;CACF;AAED,qBAAa,IAAK,SAAQ,cAAc,CAAC,cAAc;IACrD;;;;;;;OAOG;gBACS,GAAG,IAAI,EAAE,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE;IAQlD,IAAI,eAAe,cAElB;IAED;;;OAGG;IACH,IAAI,SAAS,WAEZ;IAED;;OAEG;IACH,IAAI,IAAI,WAEP;CACF;AAQD;;;;GAIG;AACH,qBAAa,SAAU,SAAQ,cAAc,CAAC,mBAAmB;IAC/D;;;;;;;OAOG;gBACS,GAAG,IAAI,EAAE,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE;IAQlD,IAAI,eAAe,cAElB;IAED;;;;OAIG;IACH,IAAI,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,EAAE;IAO5B;;OAEG;IACH,IAAI,IAAI,WAEP;CACF"}
1
+ {"version":3,"file":"FileSystem.d.ts","sourceRoot":"","sources":["../../src/next/FileSystem.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtE,OAAO,cAAc,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGhD,qBAAa,KAAM,SAAQ,aAAa;IACtC;;OAEG;IACH,MAAM,KAAK,KAAK,cAEf;IAED;;OAEG;IACH,MAAM,KAAK,QAAQ,cAElB;IACD,MAAM,KAAK,qBAAqB,8BAO/B;CACF;AAED,qBAAa,IAAK,SAAQ,cAAc,CAAC,cAAc;IACrD;;;;;;;OAOG;gBACS,GAAG,IAAI,EAAE,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE;IAQlD,IAAI,eAAe,cAElB;IAED;;;OAGG;IACH,IAAI,SAAS,WAEZ;IAED;;OAEG;IACH,IAAI,IAAI,WAEP;IAED,cAAc;IAId,cAAc;CAGf;AAQD;;;;GAIG;AACH,qBAAa,SAAU,SAAQ,cAAc,CAAC,mBAAmB;IAC/D;;;;;;;OAOG;gBACS,GAAG,IAAI,EAAE,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE;IAQlD,IAAI,eAAe,cAElB;IAED;;;;OAIG;IACH,IAAI,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,EAAE;IAO5B;;OAEG;IACH,IAAI,IAAI,WAEP;CACF"}
@@ -0,0 +1,17 @@
1
+ import type { FileHandle } from './ExpoFileSystem.types';
2
+ export declare class FileSystemReadableStreamSource implements UnderlyingByteSource {
3
+ handle: FileHandle;
4
+ size: number;
5
+ type: "bytes";
6
+ constructor(handle: any);
7
+ cancel(): void;
8
+ pull(controller: ReadableByteStreamController): void;
9
+ }
10
+ export declare class FileSystemWritableSink implements UnderlyingSink {
11
+ handle: FileHandle;
12
+ constructor(handle: any);
13
+ abort(): void;
14
+ close(): void;
15
+ write(chunk: Uint8Array): void;
16
+ }
17
+ //# sourceMappingURL=streams.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streams.d.ts","sourceRoot":"","sources":["../../src/next/streams.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEzD,qBAAa,8BAA+B,YAAW,oBAAoB;IACzE,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,EAAE,MAAM,CAAQ;IACpB,IAAI,UAAoB;gBAEZ,MAAM,KAAA;IAIlB,MAAM;IAIN,IAAI,CAAC,UAAU,EAAE,4BAA4B;CA4B9C;AAED,qBAAa,sBAAuB,YAAW,cAAc;IAC3D,MAAM,EAAE,UAAU,CAAC;gBAEP,MAAM,KAAA;IAIlB,KAAK;IAIL,KAAK;IAIL,KAAK,CAAC,KAAK,EAAE,UAAU;CAGxB"}
@@ -0,0 +1,53 @@
1
+ import Foundation
2
+ import ExpoModulesCore
3
+
4
+ internal final class FileSystemFileHandle: SharedRef<FileHandle> {
5
+ let file: FileSystemFile
6
+ let handle: FileHandle
7
+
8
+ init(file: FileSystemFile) throws {
9
+ self.file = file
10
+ handle = try FileHandle(forUpdating: file.url)
11
+ super.init(handle)
12
+ }
13
+
14
+ func read(_ length: Int) throws -> Data {
15
+ do {
16
+ let data = try handle.read(upToCount: length)
17
+ return data ?? Data()
18
+ } catch {
19
+ throw UnableToReadHandleException(error.localizedDescription)
20
+ }
21
+ }
22
+
23
+ func write(_ bytes: Data) throws {
24
+ try handle.write(contentsOf: bytes)
25
+ }
26
+
27
+ func close() throws {
28
+ try handle.close()
29
+ }
30
+
31
+ var offset: UInt64? {
32
+ get {
33
+ try? handle.offset()
34
+ }
35
+ set(newOffset) {
36
+ guard let newOffset else {
37
+ return
38
+ }
39
+ handle.seek(toFileOffset: newOffset)
40
+ }
41
+ }
42
+
43
+ var size: UInt64? {
44
+ do {
45
+ let offset = try handle.offset()
46
+ let size = try handle.seekToEnd()
47
+ handle.seek(toFileOffset: offset)
48
+ return size
49
+ } catch {
50
+ return nil
51
+ }
52
+ }
53
+ }
@@ -49,6 +49,12 @@ internal class UnableToCreateFileException: GenericException<String> {
49
49
  }
50
50
  }
51
51
 
52
+ internal class UnableToReadHandleException: GenericException<String> {
53
+ override var reason: String {
54
+ "Unable to read from a file handle: \(param)"
55
+ }
56
+ }
57
+
52
58
  internal class DestinationAlreadyExistsException: Exception {
53
59
  override var reason: String {
54
60
  "Destination already exists"
@@ -72,6 +72,10 @@ public final class FileSystemNextModule: Module {
72
72
  return try file.base64()
73
73
  }
74
74
 
75
+ Function("open") { file in
76
+ return try FileSystemFileHandle(file: file)
77
+ }
78
+
75
79
  Function("write") { (file, content: Either<String, TypedArray>) in
76
80
  if let content: String = content.get() {
77
81
  try file.write(content)
@@ -114,6 +118,30 @@ public final class FileSystemNextModule: Module {
114
118
  }
115
119
  }
116
120
 
121
+ Class(FileSystemFileHandle.self) {
122
+ Function("readBytes") { (fileHandle, bytes: Int) in
123
+ try fileHandle.read(bytes)
124
+ }
125
+
126
+ Function("writeBytes") { (fileHandle, bytes: Data) in
127
+ try fileHandle.write(bytes)
128
+ }
129
+
130
+ Function("close") { fileHandle in
131
+ try fileHandle.close()
132
+ }
133
+
134
+ Property("offset") { fileHandle in
135
+ fileHandle.offset
136
+ }.set { (fileHandle, volume: UInt64) in
137
+ fileHandle.offset = volume
138
+ }
139
+
140
+ Property("size") { fileHandle in
141
+ fileHandle.size
142
+ }
143
+ }
144
+
117
145
  Class(FileSystemDirectory.self) {
118
146
  Constructor { (url: URL) in
119
147
  return FileSystemDirectory(url: url.standardizedFileURL)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-file-system",
3
- "version": "18.0.2",
3
+ "version": "18.0.3",
4
4
  "description": "Provides access to the local file system on the device.",
5
5
  "main": "src/index.ts",
6
6
  "types": "build/index.d.ts",
@@ -42,5 +42,8 @@
42
42
  "expo": "*",
43
43
  "react-native": "*"
44
44
  },
45
- "gitHead": "8f11fad6f46b878de4746b49b18599fc57b5729f"
45
+ "dependencies": {
46
+ "web-streams-polyfill": "^3.3.2"
47
+ },
48
+ "gitHead": "c4999f5aa407155a170830a0fa17e3e3ca1fcbee"
46
49
  }
@@ -133,6 +133,12 @@ export declare class File {
133
133
  */
134
134
  move(destination: Directory | File);
135
135
 
136
+ /**
137
+ * Returns a FileHandle object that can be used to read and write data to the file.
138
+ * @throws Error if the file does not exist or cannot be opened.
139
+ */
140
+ open(): FileHandle;
141
+
136
142
  /**
137
143
  * A static method that downloads a file from the network.
138
144
  * @param url - The URL of the file to download.
@@ -155,3 +161,30 @@ export declare class File {
155
161
  */
156
162
  md5: string | null;
157
163
  }
164
+
165
+ export declare class FileHandle {
166
+ /*
167
+ * Closes the file handle. This allows the file to be deleted, moved or read by a different process. Subsequent calls to `readBytes` or `writeBytes` will throw an error.
168
+ */
169
+ close(): void;
170
+ /*
171
+ * Reads the specified amount of bytes from the file at the current offset.
172
+ * @param length - The number of bytes to read.
173
+ */
174
+ readBytes(length: number): Uint8Array;
175
+ /*
176
+ * Writes the specified bytes to the file at the current offset.
177
+ * @param bytes - A Uint8Array array containing bytes to write.
178
+ */
179
+ writeBytes(bytes: Uint8Array): void;
180
+ /*
181
+ * A property that indicates the current byte offset in the file. Calling `readBytes` or `writeBytes` will read or write a specified amount of bytes starting from this offset. The offset is incremented by the number of bytes read or written.
182
+ * The offset can be set to any value within the file size. If the offset is set to a value greater than the file size, the next write operation will append data to the end of the file.
183
+ * Null if the file handle is closed.
184
+ */
185
+ offset: number | null;
186
+ /*
187
+ * A size of the file in bytes or `null` if the file handle is closed.
188
+ */
189
+ size: number | null;
190
+ }
@@ -1,5 +1,8 @@
1
+ import { ReadableStream, WritableStream } from 'web-streams-polyfill';
2
+
1
3
  import ExpoFileSystem from './ExpoFileSystem';
2
4
  import { PathUtilities } from './pathUtilities';
5
+ import { FileSystemReadableStreamSource, FileSystemWritableSink } from './streams';
3
6
 
4
7
  export class Paths extends PathUtilities {
5
8
  /**
@@ -60,6 +63,14 @@ export class File extends ExpoFileSystem.FileSystemFile {
60
63
  get name() {
61
64
  return Paths.basename(this.uri);
62
65
  }
66
+
67
+ readableStream() {
68
+ return new ReadableStream<Uint8Array>(new FileSystemReadableStreamSource(super.open()));
69
+ }
70
+
71
+ writableStream() {
72
+ return new WritableStream<Uint8Array>(new FileSystemWritableSink(super.open()));
73
+ }
63
74
  }
64
75
 
65
76
  // Cannot use `static` keyword in class declaration because of a runtime error.
@@ -0,0 +1,64 @@
1
+ import type { FileHandle } from './ExpoFileSystem.types';
2
+
3
+ export class FileSystemReadableStreamSource implements UnderlyingByteSource {
4
+ handle: FileHandle;
5
+ size: number = 1024;
6
+ type = 'bytes' as const;
7
+
8
+ constructor(handle) {
9
+ this.handle = handle;
10
+ }
11
+
12
+ cancel() {
13
+ this.handle.close();
14
+ }
15
+
16
+ pull(controller: ReadableByteStreamController) {
17
+ const theView = controller.byobRequest?.view;
18
+ if (!theView) {
19
+ const bytes = this.handle.readBytes(this.size);
20
+ if (bytes.length === 0) {
21
+ controller.close();
22
+ return;
23
+ }
24
+ controller.enqueue(bytes);
25
+ return;
26
+ }
27
+
28
+ // TODO: Optimize by adding a native method that can write into a TypedArray at a given offset.
29
+ const bytes = this.handle.readBytes(theView.byteLength - theView.byteOffset);
30
+ if (bytes.length === 0) {
31
+ controller.close();
32
+ controller.byobRequest.respond(0);
33
+ return;
34
+ }
35
+ if (theView instanceof Uint8Array) {
36
+ theView.set(bytes, theView.byteOffset);
37
+ } else {
38
+ for (let i = 0; i < bytes.length; i++) {
39
+ theView[i + theView.byteOffset] = bytes[i];
40
+ }
41
+ }
42
+ controller.byobRequest.respond(bytes.length);
43
+ }
44
+ }
45
+
46
+ export class FileSystemWritableSink implements UnderlyingSink {
47
+ handle: FileHandle;
48
+
49
+ constructor(handle) {
50
+ this.handle = handle;
51
+ }
52
+
53
+ abort() {
54
+ this.close();
55
+ }
56
+
57
+ close() {
58
+ this.handle.close();
59
+ }
60
+
61
+ write(chunk: Uint8Array) {
62
+ this.handle.writeBytes(chunk);
63
+ }
64
+ }