github.com/go-kivik/kivik/v4@v4.3.2/x/fsdb/cdb/fs.go (about) 1 // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 // use this file except in compliance with the License. You may obtain a copy of 3 // the License at 4 // 5 // http://www.apache.org/licenses/LICENSE-2.0 6 // 7 // Unless required by applicable law or agreed to in writing, software 8 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 // License for the specific language governing permissions and limitations under 11 // the License. 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 // FS provides filesystem access to a 29 type FS struct { 30 fs filesystem.Filesystem 31 root string 32 } 33 34 // New initializes a new FS instance, anchored at dbroot. If fs is omitted or 35 // nil, the default is used. 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() // nolint: errcheck 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() // nolint: errcheck 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 // openRevs returns the active revisions for docID that match revid. 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 // OpenDocIDOpenRevs opens the requested document by ID (without file extension), 160 // same as OpenDocID, however, it honors the open_revs option, to potentially 161 // return multiple revisions of the same document. 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 // OpenDocID opens the requested document by ID (without file extension). 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 }