-
Notifications
You must be signed in to change notification settings - Fork 80
Expand file tree
/
Copy pathExpoFileSystemStorageAdapter.ts
More file actions
134 lines (116 loc) · 3.76 KB
/
Copy pathExpoFileSystemStorageAdapter.ts
File metadata and controls
134 lines (116 loc) · 3.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import { decode as decodeBase64 } from 'base64-arraybuffer';
import type { AttachmentData, StreamingLocalStorageAdapter } from '@powersync/common';
import type { File, Directory } from 'expo-file-system';
/**
* ExpoFileSystemStorageAdapter implements LocalStorageAdapter using Expo's new File System API (SDK 54+).
* Suitable for React Native applications using Expo SDK 54 or later.
*
* @experimental
* @alpha This is currently experimental and may change without a major version bump.
*/
export class ExpoFileSystemStorageAdapter implements StreamingLocalStorageAdapter {
private File: typeof File;
private Directory: typeof Directory;
private storageDir: Directory;
constructor(storageDirectory?: string) {
let fs: typeof import('expo-file-system');
try {
fs = require('expo-file-system');
} catch (e) {
throw new Error(`Could not resolve expo-file-system.
To use the Expo File System attachment adapter please install expo-file-system (SDK 54+).`);
}
if (!fs.File || !fs.Directory || !fs.Paths) {
throw new Error(`Expo File System API not available. This adapter requires expo-file-system SDK 54+.`);
}
this.File = fs.File;
this.Directory = fs.Directory;
// Default to a subdirectory in the document directory
const basePath = storageDirectory ?? fs.Paths.document;
this.storageDir = new fs.Directory(basePath, 'attachments');
}
async initialize(): Promise<void> {
if (!this.storageDir.exists) {
this.storageDir.create();
}
}
async clear(): Promise<void> {
if (this.storageDir.exists) {
this.storageDir.delete();
}
}
getLocalUri(filename: string): string {
return new this.File(this.storageDir, filename).uri;
}
async saveFile(
filePath: string,
data: AttachmentData
): Promise<number> {
const file = new this.File(filePath);
let size: number;
if (typeof data === 'string') {
// String data is assumed to be Base64 encoded
const arrayBuffer = decodeBase64(data);
const bytes = new Uint8Array(arrayBuffer);
file.write(bytes);
size = bytes.byteLength;
} else {
// Handle ArrayBuffer data
const bytes = new Uint8Array(data);
file.write(bytes);
size = bytes.byteLength;
}
return size;
}
async readFile(filePath: string, mediaType?: string): Promise<ArrayBuffer> {
const file = new this.File(filePath);
const { buffer } = await file.bytes();
return buffer;
}
async moveFile(sourceUri: string, targetUri: string): Promise<number> {
if (sourceUri !== targetUri) {
const target = new this.File(targetUri);
if (target.exists) {
target.delete();
}
new this.File(sourceUri).move(target);
}
return new this.File(targetUri).size ?? 0;
}
async deleteFile(filePath: string): Promise<void> {
try {
const file = new this.File(filePath);
if (file.exists) {
file.delete();
}
} catch (error: any) {
if (error.code === 'ENOENT' || error.message?.includes('ENOENT') || error.message?.includes('not exist')) {
return;
}
throw new Error(`Failed to delete file at ${filePath}: ${error.message}`, { cause: error });
}
}
async fileExists(filePath: string): Promise<boolean> {
try {
const file = new this.File(filePath);
return file.exists;
} catch (error: any) {
if (error.code === 'ENOENT' || error.message?.includes('ENOENT')) {
return false;
}
return false;
}
}
async makeDir(path: string): Promise<void> {
const dir = new this.Directory(path);
if (!dir.exists) {
dir.create();
}
}
async rmDir(path: string): Promise<void> {
const dir = new this.Directory(path);
if (dir.exists) {
dir.delete();
}
}
}