-
Notifications
You must be signed in to change notification settings - Fork 26
/
file_writer.go
132 lines (114 loc) · 3.42 KB
/
file_writer.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
//go:build js && wasm
// +build js,wasm
package jsbridge
import (
"errors"
"io"
"io/fs"
"syscall/js"
)
type FileWriter struct {
writableStream js.Value
uint8Array js.Value
fileHandle js.Value
bufLen int
buf []byte
bufWriteOffset int
writeError bool
}
const writeBlocks = 60
// len(p) will always be <= 64KB
func (w *FileWriter) Write(p []byte) (int, error) {
//init buffer if not initialized
if len(w.buf) == 0 {
w.buf = make([]byte, len(p)*writeBlocks)
}
//copy bytes to buf
if w.bufWriteOffset+len(p) > len(w.buf) {
w.writeError = true
return 0, io.ErrShortWrite
}
n := copy(w.buf[w.bufWriteOffset:], p)
w.bufWriteOffset += n
if w.bufWriteOffset == len(w.buf) {
//write to file
if w.bufLen != len(w.buf) {
w.bufLen = len(w.buf)
w.uint8Array = js.Global().Get("Uint8Array").New(w.bufLen)
}
js.CopyBytesToJS(w.uint8Array, w.buf)
_, err := Await(w.writableStream.Call("write", w.uint8Array))
if len(err) > 0 && !err[0].IsNull() {
w.writeError = true
return 0, errors.New("file_writer: " + err[0].String())
}
//reset buffer
w.bufWriteOffset = 0
}
return len(p), nil
}
// func (w *FileWriter) WriteAt(p []byte, offset int64) (int, error) {
// uint8Array := js.Global().Get("Uint8Array").New(len(p))
// js.CopyBytesToJS(uint8Array, p)
// options := js.Global().Get("Object").New()
// options.Set("type", "write")
// options.Set("position", offset)
// options.Set("data", uint8Array)
// options.Set("size", len(p))
// _, err := Await(w.writableStream.Call("write", options))
// if len(err) > 0 && !err[0].IsNull() {
// return 0, errors.New("file_writer: " + err[0].String())
// }
// return len(p), nil
// }
func (w *FileWriter) Close() error {
if w.bufWriteOffset > 0 && !w.writeError {
w.buf = w.buf[:w.bufWriteOffset]
uint8Array := js.Global().Get("Uint8Array").New(len(w.buf))
js.CopyBytesToJS(uint8Array, w.buf)
_, err := Await(w.writableStream.Call("write", uint8Array))
if len(err) > 0 && !err[0].IsNull() {
return errors.New("file_writer: " + err[0].String())
}
}
_, err := Await(w.writableStream.Call("close"))
if len(err) > 0 && !err[0].IsNull() {
return errors.New("file_writer: " + err[0].String())
}
return nil
}
func (w *FileWriter) Read(p []byte) (int, error) {
return 0, errors.New("file_writer: not supported")
}
func (w *FileWriter) Seek(offset int64, whence int) (int64, error) {
return 0, nil
}
func (w *FileWriter) Sync() error {
return nil
}
func (w *FileWriter) Stat() (fs.FileInfo, error) {
return nil, nil
}
func NewFileWriter(filename string) (*FileWriter, error) {
if !js.Global().Get("window").Get("showSaveFilePicker").Truthy() || !js.Global().Get("window").Get("WritableStream").Truthy() {
return nil, errors.New("file_writer: not supported")
}
showSaveFilePicker := js.Global().Get("window").Get("showSaveFilePicker")
//create options with suggested name
options := js.Global().Get("Object").New()
options.Set("suggestedName", filename)
//request a file handle
fileHandle, err := Await(showSaveFilePicker.Invoke(options))
if len(err) > 0 && !err[0].IsNull() {
return nil, errors.New("file_writer: " + err[0].String())
}
//create a writable stream
writableStream, err := Await(fileHandle[0].Call("createWritable"))
if len(err) > 0 && !err[0].IsNull() {
return nil, errors.New("file_writer: " + err[0].String())
}
return &FileWriter{
writableStream: writableStream[0],
fileHandle: fileHandle[0],
}, nil
}