go-vise

Constrained Size Output Virtual Machine
Info | Log | Files | Refs | README | LICENSE

fs.go (4306B)


      1 package fs
      2 
      3 import (
      4 	"context"
      5 	"encoding/base64"
      6 	"errors"
      7 	"fmt"
      8 	"io/fs"
      9 	"io/ioutil"
     10 	"os"
     11 	"path"
     12 
     13 	"git.defalsify.org/vise.git/db"
     14 )
     15 
     16 // holds string (filepath) versions of LookupKey
     17 type fsLookupKey struct {
     18 	Default     string
     19 	Translation string
     20 }
     21 
     22 // pure filesystem backend implementation if the Db interface.
     23 type fsDb struct {
     24 	*db.DbBase
     25 	dir         string
     26 	elements    []os.DirEntry
     27 	matchPrefix []byte
     28 	binary      bool
     29 }
     30 
     31 // NewFsDb creates a filesystem backed Db implementation.
     32 func NewFsDb() *fsDb {
     33 	db := &fsDb{
     34 		DbBase: db.NewDbBase(),
     35 	}
     36 	return db
     37 }
     38 
     39 func (fdb *fsDb) WithBinary() *fsDb {
     40 	fdb.binary = true
     41 	return fdb
     42 }
     43 
     44 // String implements the string interface.
     45 func (fdb *fsDb) String() string {
     46 	return "fsdb: " + fdb.dir
     47 }
     48 
     49 // Connect implements the Db interface.
     50 func (fdb *fsDb) Connect(ctx context.Context, connStr string) error {
     51 	if fdb.dir != "" {
     52 		logg.WarnCtxf(ctx, "already connected", "conn", fdb.dir)
     53 		return nil
     54 	}
     55 	err := os.MkdirAll(connStr, 0700)
     56 	if err != nil {
     57 		return err
     58 	}
     59 	fdb.DbBase.Connect(ctx, connStr)
     60 	fdb.dir = fdb.Connection()
     61 	return nil
     62 }
     63 
     64 // ToKey overrides the BaseDb implementation, creating a base64 string
     65 // if binary keys have been enabled
     66 func (fdb *fsDb) ToKey(ctx context.Context, key []byte) (db.LookupKey, error) {
     67 	if fdb.binary {
     68 		s := base64.StdEncoding.EncodeToString(key)
     69 		key = []byte(s)
     70 	}
     71 	return fdb.DbBase.ToKey(ctx, key)
     72 }
     73 
     74 func (fdb *fsDb) DecodeKey(ctx context.Context, key []byte) ([]byte, error) {
     75 	key, err := fdb.DbBase.DecodeKey(ctx, key)
     76 	if err != nil {
     77 		return nil, err
     78 	}
     79 	if !fdb.binary {
     80 		return key, nil
     81 	}
     82 	oldKey := key
     83 	key, err = base64.StdEncoding.DecodeString(string(key))
     84 	if err != nil {
     85 		return []byte{}, fmt.Errorf("base64 decode error '%s': %v", oldKey, err)
     86 	}
     87 	logg.TraceCtxf(ctx, "decoding base64 key", "base64", oldKey, "bin", key)
     88 	return key, nil
     89 }
     90 
     91 // Get implements the Db interface.
     92 func (fdb *fsDb) Get(ctx context.Context, key []byte) ([]byte, error) {
     93 	var f *os.File
     94 	lk, err := fdb.ToKey(ctx, key)
     95 	if err != nil {
     96 		return nil, err
     97 	}
     98 	flk, err := fdb.pathFor(ctx, &lk)
     99 	if err != nil {
    100 		return nil, err
    101 	}
    102 	flka, err := fdb.altPathFor(ctx, &lk)
    103 	if err != nil {
    104 		return nil, err
    105 	}
    106 	for i, fp := range []string{flk.Translation, flka.Translation, flk.Default, flka.Default} {
    107 		if fp == "" {
    108 			logg.TraceCtxf(ctx, "fs get skip missing", "i", i)
    109 			continue
    110 		}
    111 		logg.TraceCtxf(ctx, "trying fs get", "i", i, "key", key, "path", fp)
    112 		f, err = os.Open(fp)
    113 		if err == nil {
    114 			break
    115 		}
    116 		if !errors.Is(err, fs.ErrNotExist) {
    117 			return nil, err
    118 		}
    119 	}
    120 	if f == nil {
    121 		return nil, db.NewErrNotFound(key)
    122 	}
    123 	defer f.Close()
    124 	b, err := ioutil.ReadAll(f)
    125 	if err != nil {
    126 		return nil, err
    127 	}
    128 	return b, nil
    129 }
    130 
    131 // Put implements the Db interface.
    132 func (fdb *fsDb) Put(ctx context.Context, key []byte, val []byte) error {
    133 	if !fdb.CheckPut() {
    134 		return errors.New("unsafe put and safety set")
    135 	}
    136 	lk, err := fdb.ToKey(ctx, key)
    137 	if err != nil {
    138 		return err
    139 	}
    140 	flk, err := fdb.pathFor(ctx, &lk)
    141 	if err != nil {
    142 		return err
    143 	}
    144 	logg.TraceCtxf(ctx, "fs put", "key", key, "lk", lk, "flk", flk, "val", val)
    145 	if flk.Translation != "" {
    146 		err = ioutil.WriteFile(flk.Translation, val, 0600)
    147 		if err != nil {
    148 			return err
    149 		}
    150 		return nil
    151 	}
    152 	return ioutil.WriteFile(flk.Default, val, 0600)
    153 }
    154 
    155 // Close implements the Db interface.
    156 func (fdb *fsDb) Close(ctx context.Context) error {
    157 	return nil
    158 }
    159 
    160 // create a key safe for the filesystem.
    161 func (fdb *fsDb) pathFor(ctx context.Context, lk *db.LookupKey) (fsLookupKey, error) {
    162 	var flk fsLookupKey
    163 	lk.Default[0] += 0x30
    164 	flk.Default = path.Join(fdb.dir, string(lk.Default))
    165 	if lk.Translation != nil {
    166 		lk.Translation[0] += 0x30
    167 		flk.Translation = path.Join(fdb.dir, string(lk.Translation))
    168 	}
    169 	return flk, nil
    170 }
    171 
    172 // create a key safe for the filesystem, matching legacy resource.FsResource name.
    173 func (fdb *fsDb) altPathFor(ctx context.Context, lk *db.LookupKey) (fsLookupKey, error) {
    174 	var flk fsLookupKey
    175 	fb := string(lk.Default[1:])
    176 	if fdb.Prefix() == db.DATATYPE_BIN {
    177 		fb += ".bin"
    178 	}
    179 	flk.Default = path.Join(fdb.dir, fb)
    180 
    181 	if lk.Translation != nil {
    182 		fb = string(lk.Translation[1:])
    183 		if fdb.Prefix() == db.DATATYPE_BIN {
    184 			fb += ".bin"
    185 		}
    186 		flk.Translation = path.Join(fdb.dir, fb)
    187 	}
    188 
    189 	return flk, nil
    190 }