1
2
3
4
5
6
7
8
9
10
11
12
13 package cdb
14
15 import (
16 "errors"
17 "net/http"
18 "os"
19 "path/filepath"
20 "sort"
21 "strings"
22
23 "github.com/go-kivik/kivik/v4/driver"
24 "github.com/go-kivik/kivik/v4/x/fsdb/cdb/decode"
25 "github.com/go-kivik/kivik/v4/x/fsdb/filesystem"
26 )
27
28
29 type FS struct {
30 fs filesystem.Filesystem
31 root string
32 }
33
34
35
36 func New(dbroot string, fs ...filesystem.Filesystem) *FS {
37 var vfs filesystem.Filesystem
38 if len(fs) > 0 {
39 vfs = fs[0]
40 }
41 if vfs == nil {
42 vfs = filesystem.Default()
43 }
44 return &FS{
45 fs: vfs,
46 root: dbroot,
47 }
48 }
49
50 func (fs *FS) readMainRev(base string) (*Revision, error) {
51 f, ext, err := decode.OpenAny(fs.fs, base)
52 if err != nil {
53 return nil, kerr(missing(err))
54 }
55 defer f.Close()
56 rev := new(Revision)
57 rev.isMain = true
58 rev.path = base + "." + ext
59 rev.fs = fs.fs
60 if err := decode.Decode(f, ext, rev); err != nil {
61 return nil, err
62 }
63 if err := rev.restoreAttachments(); err != nil {
64 return nil, err
65 }
66 return rev, nil
67 }
68
69 func (fs *FS) readSubRev(path string) (*Revision, error) {
70 ext := filepath.Ext(path)
71
72 f, err := fs.fs.Open(path)
73 if err != nil {
74 return nil, kerr(missing(err))
75 }
76 defer f.Close()
77 rev := new(Revision)
78 rev.path = path
79 rev.fs = fs.fs
80 if err := decode.Decode(f, ext, rev); err != nil {
81 return nil, err
82 }
83 if err := rev.restoreAttachments(); err != nil {
84 return nil, err
85 }
86 return rev, nil
87 }
88
89 func (r *Revision) restoreAttachments() error {
90 for attname, att := range r.Attachments {
91 if att.RevPos == nil {
92 revpos := r.Rev.Seq
93 att.RevPos = &revpos
94 }
95 if att.Size == 0 || att.Digest == "" {
96 f, err := r.openAttachment(attname)
97 if err != nil {
98 return statusError{status: http.StatusInternalServerError, error: err}
99 }
100 att.Size, att.Digest = digest(f)
101 _ = f.Close()
102 }
103 }
104 return nil
105 }
106
107
108 func (fs *FS) openRevs(docID string, revIDs []string) (Revisions, error) {
109 revs := make(Revisions, 0, len(revIDs))
110 base := EscapeID(docID)
111 for _, revid := range revIDs {
112 rev, err := fs.readMainRev(filepath.Join(fs.root, base))
113 if err != nil && err != errNotFound {
114 return nil, err
115 }
116 if err == nil {
117 if revid == "" || rev.Rev.String() == revid {
118 revs = append(revs, rev)
119 }
120 }
121 dirpath := filepath.Join(fs.root, "."+base)
122 dir, err := fs.fs.Open(dirpath)
123 if err != nil && !os.IsNotExist(err) {
124 return nil, err
125 }
126 if err == nil {
127 files, err := dir.Readdir(-1)
128 if err != nil {
129 return nil, err
130 }
131 for _, info := range files {
132 if info.IsDir() {
133 continue
134 }
135 if revid != "" {
136 baseRev := strings.TrimSuffix(info.Name(), filepath.Ext(info.Name()))
137 if baseRev != revid {
138 continue
139 }
140 }
141 rev, err := fs.readSubRev(filepath.Join(dirpath, info.Name()))
142 switch {
143 case err == errUnrecognizedFile:
144 continue
145 case err != nil:
146 return nil, err
147 }
148 revs = append(revs, rev)
149 }
150 }
151 if len(revs) == 0 {
152 return nil, errNotFound
153 }
154 }
155 sort.Sort(revs)
156 return revs, nil
157 }
158
159
160
161
162 func (fs *FS) OpenDocIDOpenRevs(docID string, options driver.Options) ([]*Document, error) {
163 opts := map[string]interface{}{}
164 options.Apply(opts)
165 rev, _ := opts["rev"].(string)
166 revs, err := fs.openRevs(docID, []string{rev})
167 if err != nil {
168 return nil, err
169 }
170 if rev == "" && revs.Deleted() {
171 return nil, statusError{status: http.StatusNotFound, error: errors.New("deleted")}
172 }
173 doc := &Document{
174 ID: docID,
175 Revisions: revs,
176 cdb: fs,
177 }
178 for _, rev := range doc.Revisions {
179 for filename, att := range rev.Attachments {
180 file, err := rev.openAttachment(filename)
181 if err != nil {
182 return nil, err
183 }
184 _ = file.Close()
185 att.path = file.Name()
186 att.fs = fs.fs
187 }
188 }
189 return []*Document{doc}, nil
190 }
191
192
193 func (fs *FS) OpenDocID(docID string, options driver.Options) (*Document, error) {
194 opts := map[string]interface{}{}
195 options.Apply(opts)
196 rev, _ := opts["rev"].(string)
197 revs, err := fs.openRevs(docID, []string{rev})
198 if err != nil {
199 return nil, err
200 }
201 if rev == "" && revs.Deleted() {
202 return nil, statusError{status: http.StatusNotFound, error: errors.New("deleted")}
203 }
204 doc := &Document{
205 ID: docID,
206 Revisions: revs,
207 cdb: fs,
208 }
209 for _, rev := range doc.Revisions {
210 for filename, att := range rev.Attachments {
211 file, err := rev.openAttachment(filename)
212 if err != nil {
213 return nil, err
214 }
215 _ = file.Close()
216 att.path = file.Name()
217 att.fs = fs.fs
218 }
219 }
220 return doc, nil
221 }
222
View as plain text