snapback4-expo 0.2.1 → 0.2.2
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/README.md
CHANGED
|
@@ -38,3 +38,11 @@ in 31 ms, a reopen in 6 ms, a cold start with no network answering the
|
|
|
38
38
|
thread from the store in 2 ms, and writes composed offline surviving a
|
|
39
39
|
restart and landing in order. `EXPO_PUBLIC_SNAPBACK_TRACE=1` logs every
|
|
40
40
|
call's op, time and answer size. Android follows.
|
|
41
|
+
|
|
42
|
+
Ordinary media can be retained with `createLocalClient({ ..., device: nativeDevice,
|
|
43
|
+
assets: { keep: "viewed", maxBytes: 67108864 } })`. The opener supplies private
|
|
44
|
+
file storage beside each device store in Documents. Offline `client.asset` and
|
|
45
|
+
`useAsset` return a file URI; online calls revalidate. The client purges bytes
|
|
46
|
+
on home-row revocation, deletion, clearing or expiry and on sign-out; `once`
|
|
47
|
+
bytes never enter file storage. Rebuild the native app to include this storage
|
|
48
|
+
capability. `keep: "none"` (default) retains no bytes.
|
|
Binary file
|
|
Binary file
|
|
@@ -6,6 +6,7 @@ import Snapback4Device
|
|
|
6
6
|
// link and the transport. Handles are small integers the module hands out.
|
|
7
7
|
public final class Snapback4Module: Module {
|
|
8
8
|
private var devices: [Int: OpaquePointer] = [:]
|
|
9
|
+
private var mediaDirectories: [Int: URL] = [:]
|
|
9
10
|
private var nextHandle = 1
|
|
10
11
|
private let lock = NSLock()
|
|
11
12
|
|
|
@@ -32,6 +33,7 @@ public final class Snapback4Module: Module {
|
|
|
32
33
|
let handle = self.nextHandle
|
|
33
34
|
self.nextHandle += 1
|
|
34
35
|
self.devices[handle] = device
|
|
36
|
+
self.mediaDirectories[handle] = URL(fileURLWithPath: path + ".media", isDirectory: true)
|
|
35
37
|
return handle
|
|
36
38
|
}
|
|
37
39
|
|
|
@@ -49,14 +51,55 @@ public final class Snapback4Module: Module {
|
|
|
49
51
|
return String(cString: answer)
|
|
50
52
|
}
|
|
51
53
|
|
|
54
|
+
// @ref LLP 3000.007#8 — the opener owns ordinary media in Documents
|
|
55
|
+
AsyncFunction("readAsset") { (handle: Int, id: String) -> String? in
|
|
56
|
+
self.lock.lock()
|
|
57
|
+
defer { self.lock.unlock() }
|
|
58
|
+
let file = try self.assetFile(handle, id)
|
|
59
|
+
guard FileManager.default.fileExists(atPath: file.path) else { return nil }
|
|
60
|
+
return try Data(contentsOf: file).map { String(format: "%02x", $0) }.joined()
|
|
61
|
+
}
|
|
62
|
+
AsyncFunction("writeAsset") { (handle: Int, id: String, hex: String) in
|
|
63
|
+
self.lock.lock()
|
|
64
|
+
defer { self.lock.unlock() }
|
|
65
|
+
let file = try self.assetFile(handle, id)
|
|
66
|
+
let chars = Array(hex.utf8)
|
|
67
|
+
guard chars.count % 2 == 0 else { throw Snapback4OpenException("invalid media bytes") }
|
|
68
|
+
var bytes = Data(capacity: chars.count / 2)
|
|
69
|
+
for i in stride(from: 0, to: chars.count, by: 2) {
|
|
70
|
+
guard let byte = UInt8(String(bytes: chars[i...i+1], encoding: .utf8) ?? "", radix: 16) else { throw Snapback4OpenException("invalid media bytes") }
|
|
71
|
+
bytes.append(byte)
|
|
72
|
+
}
|
|
73
|
+
try FileManager.default.createDirectory(at: file.deletingLastPathComponent(), withIntermediateDirectories: true)
|
|
74
|
+
try bytes.write(to: file, options: .atomic)
|
|
75
|
+
}
|
|
76
|
+
AsyncFunction("removeAsset") { (handle: Int, id: String) in
|
|
77
|
+
self.lock.lock()
|
|
78
|
+
defer { self.lock.unlock() }
|
|
79
|
+
let file = try self.assetFile(handle, id)
|
|
80
|
+
if FileManager.default.fileExists(atPath: file.path) { try FileManager.default.removeItem(at: file) }
|
|
81
|
+
}
|
|
82
|
+
Function("assetUri") { (handle: Int, id: String) -> String in
|
|
83
|
+
self.lock.lock()
|
|
84
|
+
defer { self.lock.unlock() }
|
|
85
|
+
return try self.assetFile(handle, id).absoluteString
|
|
86
|
+
}
|
|
87
|
+
|
|
52
88
|
Function("close") { (handle: Int) in
|
|
53
89
|
self.lock.lock()
|
|
54
90
|
defer { self.lock.unlock() }
|
|
91
|
+
self.mediaDirectories.removeValue(forKey: handle)
|
|
55
92
|
if let device = self.devices.removeValue(forKey: handle) {
|
|
56
93
|
sb4_close(device)
|
|
57
94
|
}
|
|
58
95
|
}
|
|
59
96
|
}
|
|
97
|
+
private func assetFile(_ handle: Int, _ id: String) throws -> URL {
|
|
98
|
+
guard let directory = mediaDirectories[handle], !id.isEmpty,
|
|
99
|
+
id.utf8.allSatisfy({ ($0 >= 48 && $0 <= 57) || ($0 >= 65 && $0 <= 90) || ($0 >= 97 && $0 <= 122) })
|
|
100
|
+
else { throw Snapback4OpenException("invalid media id or closed device") }
|
|
101
|
+
return directory.appendingPathComponent(id)
|
|
102
|
+
}
|
|
60
103
|
}
|
|
61
104
|
|
|
62
105
|
internal final class Snapback4OpenException: GenericException<String> {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "snapback4-expo",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Snapback 4's native module for Expo: the device in Rust (the partition in its own SQLite, the stream applied, the outbox kept, queries and predictions answered) behind one JSON call, and the opener `snapback4/native` takes.",
|
|
6
6
|
"main": "src/index.ts",
|
package/src/index.ts
CHANGED
|
@@ -10,6 +10,10 @@ interface Snapback4Native {
|
|
|
10
10
|
open(path: string, backend: string | null): Promise<number>;
|
|
11
11
|
call(handle: number, request: string): Promise<string>;
|
|
12
12
|
close(handle: number): void;
|
|
13
|
+
readAsset(handle: number, id: string): Promise<string | null>;
|
|
14
|
+
writeAsset(handle: number, id: string, hex: string): Promise<void>;
|
|
15
|
+
removeAsset(handle: number, id: string): Promise<void>;
|
|
16
|
+
assetUri(handle: number, id: string): string;
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
const native = requireOptionalNativeModule<Snapback4Native>("Snapback4");
|
|
@@ -31,6 +35,12 @@ export const nativeDevice: DeviceOpener = {
|
|
|
31
35
|
if (!native) throw new Error("snapback4-expo: the native module is not in this build (Expo Go loads no custom native code; make a development build)");
|
|
32
36
|
const handle = await native.open(`${native.documentDirectory.replace(/\/$/, "")}/${name}.sqlite`, backend ? JSON.stringify(backend) : null);
|
|
33
37
|
return {
|
|
38
|
+
media: {
|
|
39
|
+
read: (id) => native.readAsset(handle, id),
|
|
40
|
+
write: (id, hex) => native.writeAsset(handle, id, hex),
|
|
41
|
+
remove: (id) => native.removeAsset(handle, id),
|
|
42
|
+
uri: (id) => native.assetUri(handle, id),
|
|
43
|
+
},
|
|
34
44
|
async call(request) {
|
|
35
45
|
const t0 = trace ? Date.now() : 0;
|
|
36
46
|
const answer = await native.call(handle, JSON.stringify(request));
|