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 +6 -0
- package/android/build.gradle +2 -2
- package/android/src/main/java/expo/modules/filesystem/next/FileSystemFileHandle.kt +68 -0
- package/android/src/main/java/expo/modules/filesystem/next/FileSystemNextExceptions.kt +10 -0
- package/android/src/main/java/expo/modules/filesystem/next/FileSystemNextModule.kt +27 -0
- package/android/src/main/java/expo/modules/filesystem/next/FileSystemPath.kt +1 -1
- package/build/next/ExpoFileSystem.types.d.ts +12 -0
- package/build/next/ExpoFileSystem.types.d.ts.map +1 -1
- package/build/next/FileSystem.d.ts +3 -0
- package/build/next/FileSystem.d.ts.map +1 -1
- package/build/next/streams.d.ts +17 -0
- package/build/next/streams.d.ts.map +1 -0
- package/ios/Next/FileSystemFileHandle.swift +53 -0
- package/ios/Next/FileSystemNextExceptions.swift +6 -0
- package/ios/Next/FileSystemNextModule.swift +28 -0
- package/package.json +5 -2
- package/src/next/ExpoFileSystem.types.ts +33 -0
- package/src/next/FileSystem.ts +11 -0
- package/src/next/streams.ts +64 -0
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._
|
package/android/build.gradle
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
apply plugin: 'com.android.library'
|
|
2
2
|
|
|
3
3
|
group = 'host.exp.exponent'
|
|
4
|
-
version = '18.0.
|
|
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.
|
|
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;
|
|
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.
|
|
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
|
-
"
|
|
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
|
+
}
|
package/src/next/FileSystem.ts
CHANGED
|
@@ -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
|
+
}
|