...

Source file src/github.com/go-kivik/kivik/v4/x/fsdb/cdb/fs.go

Documentation: github.com/go-kivik/kivik/v4/x/fsdb/cdb

     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  }
   222  

View as plain text