/
bundle.go
198 lines (177 loc) · 5.05 KB
/
bundle.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
// Interface for accessing the bundle
package bundle
import (
"bytes"
"compress/gzip"
"encoding/base64"
"io"
"sort"
"strings"
)
// Falgs for *Entry.Open and *Entry.Decode
const (
NODC int = 1 << iota // Do not decompress data
)
// The Entry type is used to represent the bundled file data. One such
// structure is used for every bundled file. All structures are kept
// in a global slice. When files are included in a bundle the terms
// "entry" and "file" may be used interchangeably (i.e. the bundle
// contains 5 files / entries).
type Entry struct {
// The name of the entry
Name string
// The size of the entry in bytes (size of "Data"). This the
// original data size, before compression and encoding.
Size int
// Is the entry compressed?
Gzip bool
// Entry data compressed (if Gzip is true) and base64 encoded
Data string
}
// Index is the type of the global map of names to entries. Such a map
// is declared for every bundle.
type Index map[string]*Entry
// MkIndex creates and initializes the names-to-entries index. A call
// to MkIndex is inserted automatically by "mkbundle" to the "init"
// function of the generated file.
func MkIndex(bundle []Entry) Index {
var bsz int
var idx Index
bsz = len(bundle)
idx = make(Index, bsz)
for i := 0; i < bsz; i++ {
idx[bundle[i].Name] = &bundle[i]
}
return idx
}
// The Has method returns true if the bundle has an entry with the
// given name
func (idx Index) Has(name string) bool {
_, ok := idx[name]
return ok
}
// The Entry method returns a pointer to the entry with the requested
// name, if such an entry exists in the bundle, or nil if no such
// entry exists.
func (idx Index) Entry(name string) *Entry {
e, ok := idx[name]
if !ok {
return nil
}
return e
}
// Dir is a slice of pointers to entries. It implements sort.Interface
type Dir []*Entry
// Len returns the length of Dir (number of elements)
func (d Dir) Len() int {
return len(d)
}
// Swap swaps entries i and j in Dir
func (d Dir) Swap(i, j int) {
t := d[i]
d[i] = d[j]
d[j] = t
}
// Less returns d[i].Name < d[j].Name
func (d Dir) Less(i, j int) bool {
return d[i].Name < d[j].Name
}
// The Dir method returns a Dir (slice of Entry pointers) of all the
// entries whose names match the given prefix (all entries whose names
// start with string "prefix")
func (idx Index) Dir(prefix string) []*Entry {
var dir Dir
for _, e := range idx {
if strings.HasPrefix(e.Name, prefix) {
dir = append(dir, e)
}
}
sort.Sort(dir)
return dir
}
// Decode returns the decoded data for the bundle entry. Returns a
// slice of bytes with the decoded, decompressed (if required), ready
// to use entry data, and an error indication which is not-nil if the
// data cannot be decoded. If argument "flag" is NODC, and the entry
// data are compressed (Entry.Gzip == true), Decode will not
// decompress the data it returns (it will only decode them). In most
// cases it is preferable to use the Reader interface instead of
// calling Decode.
func (e *Entry) Decode(flag int) ([]byte, error) {
var rs *strings.Reader
var r64 io.Reader
var rz *gzip.Reader
var buf *bytes.Buffer
var err error
rs = strings.NewReader(e.Data)
r64 = base64.NewDecoder(base64.StdEncoding, rs)
if e.Gzip && (flag&NODC == 0) {
rz, err = gzip.NewReader(r64)
if err != nil {
return nil, err
}
defer rz.Close()
} else {
rz = nil
}
buf = new(bytes.Buffer)
if rz != nil {
_, err = io.Copy(buf, rz)
} else {
_, err = io.Copy(buf, r64)
}
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// A Reader implents the io.Reader and io.Closer interface by reading,
// decoding, and decompressing (if required) data from a bundle entry.
type Reader struct {
rs *strings.Reader
r64 io.Reader
rz *gzip.Reader
}
// Open intializes and returns a Reader that reads from the bundle
// entry. It returns an error if the reader cannot be initialized. If
// argument "flag" is NODC, and the entry data are compressed
// (Entry.Gzip == true), the reader will not decompress the data read
// from it (it will only decode them).
func (e *Entry) Open(flag int) (*Reader, error) {
var br *Reader
var err error
br = &Reader{}
br.rs = strings.NewReader(e.Data)
br.r64 = base64.NewDecoder(base64.StdEncoding, br.rs)
if e.Gzip && (flag&NODC == 0) {
br.rz, err = gzip.NewReader(br.r64)
if err != nil {
return nil, err
}
} else {
br.rz = nil
}
return br, nil
}
// The Read method is used to read data from a bundle entry. Read
// fills slice "p" with decoded, decompressed, ready to use
// data. Returns the number of bytes read (stored in "p") and an error
// indication (which is not-nil when a read error has occured).
func (br *Reader) Read(p []byte) (int, error) {
if br.rz != nil {
return br.rz.Read(p)
} else {
return br.r64.Read(p)
}
}
// The Close method is Used to terminate the operation of the
// Reader. Returns an error indication which is not-nil if an error
// has occured during close. After calling Close no other operations
// must be performed on this Reader.
func (br *Reader) Close() error {
if br.rz != nil {
return br.rz.Close()
} else {
return nil
}
}