/
blob.go
285 lines (254 loc) · 5.91 KB
/
blob.go
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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
//go:build wasm
// +build wasm
// Package idbblob contains a JavaScript implementation of blob.Blob.
package idbblob
import (
"errors"
"fmt"
"sync/atomic"
"syscall/js"
"github.com/hack-pad/hackpadfs/internal/jswrapper"
"github.com/hack-pad/hackpadfs/keyvalue/blob"
"github.com/hack-pad/safejs"
)
var uint8Array safejs.Value
func init() {
var err error
uint8Array, err = safejs.Global().Get("Uint8Array")
if err != nil {
panic(err)
}
}
var (
_ interface {
blob.Blob
blob.ViewBlob
blob.SliceBlob
blob.SetBlob
blob.GrowBlob
blob.TruncateBlob
} = &Blob{}
)
// Blob is a blob.Blob for JS environments, optimized for reading and writing to a Uint8Array without en/decoding back and forth.
type Blob struct {
bytes atomic.Value // *blob.Bytes
jsValue atomic.Value // safejs.Value
length int64
}
// New creates a Blob wrapping the given JS Uint8Array buffer.
func New(unsafeBuf js.Value) (*Blob, error) {
buf := safejs.Safe(unsafeBuf)
truthy, err := buf.Truthy()
if err != nil {
return nil, err
}
if !truthy {
return FromBlob(blob.NewBytes(nil)), nil
}
instanceOf, err := buf.InstanceOf(uint8Array)
if err != nil {
return nil, err
}
if !instanceOf {
return nil, fmt.Errorf("invalid JS array type: %v", buf)
}
return newBlob(buf)
}
// NewLength creates a zero-value Blob with 'length' bytes.
func NewLength(length int) (*Blob, error) {
jsBuf, err := uint8Array.New(length)
if err != nil {
return nil, err
}
return newBlob(jsBuf)
}
func newBlob(buf safejs.Value) (*Blob, error) {
b := &Blob{}
b.jsValue.Store(buf)
length, err := buf.Length()
if err != nil {
return nil, err
}
atomic.StoreInt64(&b.length, int64(length))
return b, nil
}
// FromBlob creates a Blob from the given blob.Blob, either wrapping the JS value or copying the bytes if incompatible.
func FromBlob(b blob.Blob) *Blob {
blob, err := fromBlob(b)
if err != nil {
panic(err)
}
return blob
}
func fromBlob(b blob.Blob) (*Blob, error) {
if b, ok := b.(jswrapper.Wrapper); ok {
value := safejs.Safe(b.JSValue())
return newBlob(value)
}
buf := b.Bytes()
jsBuf, err := uint8Array.New(len(buf))
if err != nil {
return nil, err
}
_, err = safejs.CopyBytesToJS(jsBuf, buf)
if err != nil {
return nil, err
}
return newBlob(jsBuf)
}
func (b *Blob) currentBytes() *blob.Bytes {
buf := b.bytes.Load()
if buf != nil {
return buf.(*blob.Bytes)
}
return nil
}
// Bytes implememts blob.Blob
func (b *Blob) Bytes() []byte {
buf, err := b.getBytes()
if err != nil {
panic(err)
}
return buf
}
func (b *Blob) getBytes() ([]byte, error) {
if buf := b.currentBytes(); buf != nil {
return buf.Bytes(), nil
}
jsBuf := b.jsValue.Load().(safejs.Value)
length, err := jsBuf.Length()
if err != nil {
return nil, err
}
buf := make([]byte, length)
_, err = safejs.CopyBytesToGo(buf, jsBuf)
if err != nil {
return nil, err
}
b.bytes.Store(blob.NewBytes(buf))
return buf, nil
}
// JSValue implements jswrapper.Wrapper
func (b *Blob) JSValue() js.Value {
value := b.jsValue.Load().(safejs.Value)
return safejs.Unsafe(value)
}
// Len implements blob.Blob
func (b *Blob) Len() int {
return int(atomic.LoadInt64(&b.length))
}
// View implements blob.ViewBlob
func (b *Blob) View(start, end int64) (blob.Blob, error) {
if start == 0 && end == atomic.LoadInt64(&b.length) {
return b, nil
}
var newBlob *Blob
value := safejs.Safe(b.JSValue())
subarray, err := value.Call("subarray", start, end)
if err != nil {
return nil, err
}
newBlob, err = New(safejs.Unsafe(subarray))
if err != nil {
return nil, err
}
if buf := b.currentBytes(); buf != nil {
newBytesBlob, err := b.currentBytes().View(start, end)
if err != nil {
return nil, err
}
newBlob.bytes.Store(newBytesBlob)
}
return newBlob, nil
}
// Slice implements blob.SliceBlob
func (b *Blob) Slice(start, end int64) (blob.Blob, error) {
if start < 0 || start > int64(b.Len()) {
return nil, fmt.Errorf("Start index out of bounds: %d", start)
}
if end < 0 || end > int64(b.Len()) {
return nil, fmt.Errorf("End index out of bounds: %d", end)
}
value := safejs.Safe(b.JSValue())
slice, err := value.Call("slice", start, end)
if err != nil {
return nil, err
}
newBlob, err := New(safejs.Unsafe(slice))
if err != nil {
return nil, err
}
if buf := b.currentBytes(); buf != nil {
newBytes, err := buf.Slice(start, end)
if err != nil {
return nil, err
}
newBlob.bytes.Store(newBytes)
}
return newBlob, nil
}
// Set implements blob.SetBlob
func (b *Blob) Set(src blob.Blob, destStart int64) (n int, err error) {
if destStart < 0 {
return 0, errors.New("negative offset")
}
if destStart >= int64(b.Len()) && destStart == 0 && src.Len() > 0 {
return 0, fmt.Errorf("Offset out of bounds: %d", destStart)
}
bValue := safejs.Safe(b.JSValue())
srcValue := safejs.Safe(FromBlob(src).JSValue())
_, err = bValue.Call("set", srcValue, destStart)
if err != nil {
return 0, err
}
n = src.Len()
if buf := b.currentBytes(); buf != nil {
_, err := buf.Set(src, destStart)
if err != nil {
return 0, err
}
}
return n, nil
}
// Grow implements blob.GrowBlob
func (b *Blob) Grow(off int64) error {
newLength := atomic.LoadInt64(&b.length) + off
buf := b.jsValue.Load().(safejs.Value)
biggerBuf, err := uint8Array.New(newLength)
if err != nil {
return err
}
_, err = biggerBuf.Call("set", buf, 0)
if err != nil {
return err
}
b.jsValue.Store(biggerBuf)
atomic.StoreInt64(&b.length, newLength)
if buf := b.currentBytes(); buf != nil {
err := buf.Grow(off)
if err != nil {
return err
}
}
return nil
}
// Truncate implements TruncateBlob
func (b *Blob) Truncate(size int64) error {
if atomic.LoadInt64(&b.length) < size {
return nil
}
value := safejs.Safe(b.JSValue())
smallerBuf, err := value.Call("slice", 0, size)
if err != nil {
return err
}
b.jsValue.Store(smallerBuf)
atomic.StoreInt64(&b.length, size)
if buf := b.currentBytes(); buf != nil {
err := buf.Truncate(size)
if err != nil {
return err
}
}
return nil
}