...

Source file src/github.com/go-kivik/kivik/v4/x/fsdb/cdb/util.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  	"crypto/md5"
    17  	"encoding/base64"
    18  	"encoding/json"
    19  	"io"
    20  	"path/filepath"
    21  	"strings"
    22  	"sync"
    23  
    24  	"github.com/go-kivik/kivik/v4/x/fsdb/filesystem"
    25  )
    26  
    27  var reservedKeys = map[string]struct{}{
    28  	"_id":                {},
    29  	"_rev":               {},
    30  	"_attachments":       {},
    31  	"_revisions":         {},
    32  	"_revs_info":         {}, // *
    33  	"_deleted":           {},
    34  	"_conflicts":         {}, // *
    35  	"_deleted_conflicts": {}, // *
    36  	"_local_seq":         {}, // *
    37  	// * Can these be PUT?
    38  }
    39  
    40  // EscapeID escapes a document or database ID to be a safe filename.
    41  func EscapeID(s string) string {
    42  	if s == "" {
    43  		return s
    44  	}
    45  	if strings.Contains(s, "/") || strings.Contains(s, "%2F") || strings.Contains(s, "%2f") {
    46  		s = strings.ReplaceAll(s, "%", "%25")
    47  		s = strings.ReplaceAll(s, "/", "%2F")
    48  	}
    49  	return s
    50  }
    51  
    52  // UnescapeID unescapes a filename into a document or database ID.
    53  func UnescapeID(s string) string {
    54  	if s == "" {
    55  		return s
    56  	}
    57  	if strings.Contains(s, "%2F") || strings.Contains(s, "%2f") || strings.Contains(s, "%25") {
    58  		s = strings.ReplaceAll(s, "%2F", "/")
    59  		s = strings.ReplaceAll(s, "%2f", "/")
    60  		s = strings.ReplaceAll(s, "%25", "%")
    61  	}
    62  	return s
    63  }
    64  
    65  // copyDigest works the same as io.Copy, but also returns the md5sum
    66  // digest of the copied file.
    67  func copyDigest(tgt io.Writer, dst io.Reader) (int64, string, error) {
    68  	h := md5.New()
    69  	tee := io.TeeReader(dst, h)
    70  	wg := sync.WaitGroup{}
    71  	wg.Add(1)
    72  	var written int64
    73  	var err error
    74  	go func() {
    75  		written, err = io.Copy(tgt, tee)
    76  		wg.Done()
    77  	}()
    78  	wg.Wait()
    79  
    80  	return written, "md5-" + base64.StdEncoding.EncodeToString(h.Sum(nil)), err
    81  }
    82  
    83  func digest(r io.Reader) (int64, string) {
    84  	h := md5.New()
    85  	written, _ := io.Copy(h, r)
    86  	return written, "md5-" + base64.StdEncoding.EncodeToString(h.Sum(nil))
    87  }
    88  
    89  func joinJSON(objects ...json.RawMessage) []byte {
    90  	var size int
    91  	for _, obj := range objects {
    92  		size += len(obj)
    93  	}
    94  	result := make([]byte, 0, size)
    95  	result = append(result, '{')
    96  	for _, obj := range objects {
    97  		if len(obj) == 4 && string(obj) == "null" {
    98  			continue
    99  		}
   100  		result = append(result, obj[1:len(obj)-1]...)
   101  		result = append(result, ',')
   102  	}
   103  	result[len(result)-1] = '}'
   104  	return result
   105  }
   106  
   107  func atomicWriteFile(fs filesystem.Filesystem, path string, r io.Reader) error {
   108  	f, err := fs.TempFile(filepath.Dir(path), ".tmp."+filepath.Base(path)+"-")
   109  	if err != nil {
   110  		return err
   111  	}
   112  	if _, err := io.Copy(f, r); err != nil {
   113  		return err
   114  	}
   115  	if err := f.Close(); err != nil {
   116  		return err
   117  	}
   118  	return fs.Rename(f.Name(), path)
   119  }
   120  
   121  type atomicWriter struct {
   122  	fs   filesystem.Filesystem
   123  	path string
   124  	f    filesystem.File
   125  	err  error
   126  }
   127  
   128  func (w *atomicWriter) Write(p []byte) (int, error) {
   129  	if w.err != nil {
   130  		return 0, w.err
   131  	}
   132  	return w.f.Write(p)
   133  }
   134  
   135  func (w *atomicWriter) Close() error {
   136  	if w.err != nil {
   137  		return w.err
   138  	}
   139  	if err := w.f.Close(); err != nil {
   140  		return err
   141  	}
   142  	return w.fs.Rename(w.f.Name(), w.path)
   143  }
   144  
   145  // atomicFileWriter returns an io.WriteCloser, which writes to a temp file, then
   146  // when Close() is called, it renames to the originally requested path.
   147  func atomicFileWriter(fs filesystem.Filesystem, path string) io.WriteCloser {
   148  	f, err := fs.TempFile(filepath.Dir(path), ".tmp."+filepath.Base(path)+"-")
   149  	return &atomicWriter{
   150  		fs:   fs,
   151  		path: path,
   152  		f:    f,
   153  		err:  err,
   154  	}
   155  }
   156  

View as plain text