1
2
3
4
5
6
7
8
9
10
11
12
13 package cdb
14
15 import (
16 "bytes"
17 "encoding/json"
18 "errors"
19 "io"
20 "path/filepath"
21
22 "github.com/go-kivik/kivik/v4/driver"
23 "github.com/go-kivik/kivik/v4/x/fsdb/filesystem"
24 )
25
26
33
34
35 type Attachment struct {
36 ContentType string `json:"content_type" yaml:"content_type"`
37 RevPos *int64 `json:"revpos,omitempty" yaml:"revpos,omitempty"`
38 Stub bool `json:"stub,omitempty" yaml:"stub,omitempty"`
39 Follows bool `json:"follows,omitempty" yaml:"follows,omitempty"`
40 Content []byte `json:"data,omitempty" yaml:"content,omitempty"`
41 Size int64 `json:"length" yaml:"size"`
42 Digest string `json:"digest" yaml:"digest"`
43
44
45
46 path string
47
48 fs filesystem.Filesystem
49
50
51
52
53 outputStub bool
54 }
55
56
57 func (a *Attachment) Open() (filesystem.File, error) {
58 if a.path == "" {
59 return nil, errors.New("no path defined")
60 }
61 return a.fs.Open(a.path)
62 }
63
64
65 func (a *Attachment) MarshalJSON() ([]byte, error) {
66 var err error
67 switch {
68 case len(a.Content) != 0:
69 a.setMetadata()
70 case a.outputStub || a.Follows:
71 err = a.readMetadata()
72 default:
73 err = a.readContent()
74 }
75 if err != nil {
76 return nil, err
77 }
78 att := struct {
79 Attachment
80 Content *[]byte `json:"data,omitempty"`
81 Stub *bool `json:"stub,omitempty"`
82 Follows *bool `json:"follows,omitempty"`
83 }{
84 Attachment: *a,
85 }
86 switch {
87 case a.outputStub:
88 att.Stub = &a.outputStub
89 case a.Follows:
90 att.Follows = &a.Follows
91 case len(a.Content) > 0:
92 att.Content = &a.Content
93 }
94 return json.Marshal(att)
95 }
96
97 func (a *Attachment) readContent() error {
98 f, err := a.fs.Open(a.path)
99 if err != nil {
100 return err
101 }
102 buf := &bytes.Buffer{}
103 a.Size, a.Digest, err = copyDigest(buf, f)
104 if err != nil {
105 return err
106 }
107 a.Content = buf.Bytes()
108 return nil
109 }
110
111 func (a *Attachment) readMetadata() error {
112 if a.path == "" {
113 return nil
114 }
115 f, err := a.fs.Open(a.path)
116 if err != nil {
117 return err
118 }
119 a.Size, a.Digest = digest(f)
120 return nil
121 }
122
123 func (a *Attachment) setMetadata() {
124 a.Size, a.Digest = digest(bytes.NewReader(a.Content))
125 }
126
127 func (a *Attachment) persist(path, attname string) error {
128 target := filepath.Join(path, attname)
129 if err := atomicWriteFile(a.fs, target, bytes.NewReader(a.Content)); err != nil {
130 return err
131 }
132 a.Content = nil
133 a.path = target
134 return nil
135 }
136
137 type attsIter []*driver.Attachment
138
139 var _ driver.Attachments = &attsIter{}
140
141 func (i attsIter) Close() error {
142 for _, att := range i {
143 if err := att.Content.Close(); err != nil {
144 return err
145 }
146 }
147 return nil
148 }
149
150 func (i *attsIter) Next(att *driver.Attachment) error {
151 if len(*i) == 0 {
152 return io.EOF
153 }
154 var next *driver.Attachment
155 next, *i = (*i)[0], (*i)[1:]
156 *att = *next
157 return nil
158 }
159
160
161
162 func (r *Revision) AttachmentsIterator() (driver.Attachments, error) {
163 if attachments, _ := r.options["attachments"].(bool); !attachments {
164 return nil, nil
165 }
166 if accept, _ := r.options["header:accept"].(string); accept == "application/json" {
167 return nil, nil
168 }
169 iter := make(attsIter, 0, len(r.Attachments))
170 for filename, att := range r.Attachments {
171 f, err := att.Open()
172 if err != nil {
173 return nil, err
174 }
175 drAtt := &driver.Attachment{
176 Filename: filename,
177 Content: f,
178 ContentType: att.ContentType,
179 Stub: att.Stub,
180 Follows: att.Follows,
181 Size: att.Size,
182 Digest: att.Digest,
183 }
184 if att.RevPos != nil {
185 drAtt.RevPos = *att.RevPos
186 }
187 iter = append(iter, drAtt)
188 }
189 return &iter, nil
190 }
191
View as plain text