gopherjs - files/files.go
1 // Package files turns javascript files into io.Reader's
2 package files // import "vimagination.zapto.org/gopherjs/files"
3
4 import (
5 "errors"
6 "io"
7 "time"
8
9 "honnef.co/go/js/dom"
10
11 "github.com/gopherjs/gopherjs/js"
12 )
13
14 // Blob is a wrapped javascript blob
15 type Blob struct {
16 b *js.Object
17 }
18
19 // NewBlob wraps a javascript blob
20 func NewBlob(b *js.Object) Blob {
21 return Blob{b}
22 }
23
24 // Len returns the length of the blob
25 func (b Blob) Len() int {
26 return b.b.Get("size").Int()
27 }
28
29 // Type returns the type of the blob
30 func (b Blob) Type() string {
31 return b.b.Get("type").String()
32 }
33
34 // Slice returns a new blob sliced with the given indicies
35 func (b Blob) Slice(i, j int) Blob {
36 return NewBlob(b.b.Call("slice", i, j))
37 }
38
39 // File wraps a Blob
40 type File struct {
41 Blob
42 }
43
44 // NewFile wraps a javascript File to access
45 func NewFile(f *dom.File) File {
46 return File{NewBlob(f.Object)}
47 }
48
49 // LastModifiedDate returns the last modified time for the file
50 func (f File) LastModifiedDate() time.Time {
51 return f.b.Get("lastModifiedDate").Interface().(time.Time)
52 }
53
54 // Name returns the files name
55 func (f File) Name() string {
56 return f.b.Get("name").String()
57 }
58
59 // FileReader wraps a File to allow for reading
60 type FileReader struct {
61 File
62 fr *js.Object
63 offset, size int64
64 }
65
66 // NewFileReader opens a File for reading
67 func NewFileReader(f File) *FileReader {
68 return &FileReader{
69 f,
70 js.Global.Get("FileReader").New(),
71 0,
72 int64(f.Len()),
73 }
74 }
75
76 // Close implements the io.Closer interface
77 func (fr *FileReader) Close() error {
78 fr.fr = nil
79 return nil
80 }
81
82 // Read implements the io.Reader interface
83 func (fr *FileReader) Read(b []byte) (int, error) {
84 n, err := fr.ReadAt(b, int64(fr.offset))
85 fr.offset += int64(n)
86 return n, err
87 }
88
89 //ReadAt implements the io.ReaderAt interface
90 func (fr *FileReader) ReadAt(b []byte, off int64) (int, error) {
91 if fr.fr == nil {
92 return 0, ErrClosed
93 }
94 if off >= fr.size {
95 return 0, io.EOF
96 }
97
98 type readResult struct {
99 size int
100 err error
101 }
102
103 c := make(chan readResult)
104 fr.fr.Set("onloadend", func(*js.Object) {
105 arr := js.Global.Get("Uint8Array").New(fr.fr.Get("result"))
106 buf := arr.Interface().([]byte)
107 go func() {
108 if len(buf) == 0 {
109 c <- readResult{0, io.EOF}
110 } else {
111 copy(b, buf)
112 c <- readResult{len(buf), nil}
113 }
114 }()
115 })
116 e := off + int64(len(b))
117 if e > fr.size {
118 e = fr.size
119 }
120 blob := fr.b.Call("slice", fr.offset, e)
121 fr.fr.Call("readAsArrayBuffer", blob)
122 r := <-c
123 return r.size, r.err
124 }
125
126 // Seek implements the io.Seeker interface
127 func (fr *FileReader) Seek(offset int64, whence int) (int64, error) {
128 if fr.fr == nil {
129 return 0, ErrClosed
130 }
131 switch whence {
132 case 0:
133 fr.offset = offset
134 case 1:
135 fr.offset += offset
136 case 2:
137 fr.offset = fr.size + offset
138 }
139 if fr.offset < 0 {
140 fr.offset = 0
141 }
142 return fr.offset, nil
143 }
144
145 // Errors
146 var (
147 ErrClosed = errors.New("file closed")
148 )