-
Notifications
You must be signed in to change notification settings - Fork 6
/
native.ts
154 lines (149 loc) · 4.69 KB
/
native.ts
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
/// <reference path="./native.d.ts" />
import { asyncIterator2ReadableStream } from '../../utils';
import type { FileSystemDriver, FileSystemNode } from './index';
const processError = (expectDirectory: boolean, err: any) => {
if (err instanceof Error && typeof (err.name as any) === 'string')
switch (err.name) {
case 'NotFoundError':
return 'ENOTFOUND';
case 'TypeMismatchError':
return expectDirectory
? 'ENOTADIR'
: 'EISDIR';
case 'InvalidModificationError':
return 'ENOTEMPTY';
case 'SecurityError':
case 'TypeError':
case 'NotAllowedError':
return 'EACCESS';
}
throw err;
}
export class NativeFS implements FileSystemDriver {
#root: FileSystemDirectoryHandle;
constructor(root: FileSystemDirectoryHandle) {
this.#root = root;
}
async resolveUri(path: string[]): Promise<string> {
const file = path.pop();
if (!file)
throw new Error('EISDIR');
let parent = this.#root;
try {
for (const segment of path)
parent = await parent.getDirectoryHandle(segment, { create: false });
} catch (err) {
throw new Error(processError(true, err));
}
try {
const found = await parent.getFileHandle(file, { create: false });
return URL.createObjectURL(await found.getFile());
} catch (err) {
throw new Error(processError(false, err));
}
}
async access(path: string[]): Promise<boolean> {
const file = path.pop();
if (!file)
throw new Error('EISDIR');
let parent = this.#root;
try {
for (const segment of path)
parent = await parent.getDirectoryHandle(segment, { create: false });
} catch (err) {
const msg = processError(true, err);
if (msg === 'ENOTFOUND')
return false;
throw new Error(msg);
}
try {
const found = await parent.getFileHandle(file, { create: false });
return (await found.queryPermission({ mode: 'read' })) === 'granted';
} catch (err) {
const msg = processError(false, err);
if (msg === 'ENOTFOUND')
return false;
throw new Error(msg);
}
}
async readDir(path: string[]): Promise<ReadableStream<FileSystemNode>> {
let parent = this.#root;
try {
for (const segment of path)
parent = await parent.getDirectoryHandle(segment, { create: false });
} catch (err) {
throw new Error(processError(true, err));
}
try {
return asyncIterator2ReadableStream(parent.entries()[Symbol.asyncIterator](), ([name, entry]) => ({
type: entry.kind,
name
}));
} catch (err) {
throw new Error(processError(false, err));
}
}
async readFile(path: string[], offset?: number, length?: number): Promise<ReadableStream<Uint8Array>> {
const file = path.pop();
if (!file)
throw new Error('EISDIR');
let parent = this.#root;
try {
for (const segment of path)
parent = await parent.getDirectoryHandle(segment, { create: false });
} catch (err) {
throw new Error(processError(true, err));
}
try {
const found = await parent.getFileHandle(file, { create: false });
return (await found.getFile()).stream();
} catch (err) {
throw new Error(processError(false, err));
}
}
async writeFile(path: string[], offset: 'before' | 'after' | 'override', create: boolean): Promise<WritableStream<Uint8Array>> {
const file = path.pop();
if (!file)
throw new Error('EISDIR');
let parent = this.#root;
try {
for (const segment of path)
parent = await parent.getDirectoryHandle(segment, { create });
} catch (err) {
throw new Error(processError(true, err));
}
try {
const found = await parent.getFileHandle(file, { create });
const buffer = new TransformStream<Uint8Array, Uint8Array>({});
buffer.readable
.pipeTo(await found.createWritable({ keepExistingData: offset !== 'override' }))
.catch(() => null);
return buffer.writable;
} catch (err) {
throw new Error(processError(false, err));
}
}
async deleteNode(path: string[], recursive: boolean): Promise<void> {
const file = path.pop();
if (!file)
throw new Error('EBUSY');
let parent = this.#root;
try {
for (const segment of path)
parent = await parent.getDirectoryHandle(segment, { create: false });
} catch (err) {
const msg = processError(true, err);
if (msg === 'ENOTFOUND')
return;
throw new Error(msg);
}
try {
await parent.removeEntry(file, { recursive });
} catch (err) {
const msg = processError(false, err);
if (msg === 'ENOTFOUND')
return;
throw new Error(msg);
}
}
}