go-vise

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

fs.go (4387B)


      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 // Base implements Db
     40 func (fdb *fsDb) Base() *db.DbBase {
     41 	return fdb.DbBase
     42 }
     43 
     44 func (fdb *fsDb) WithBinary() *fsDb {
     45 	fdb.binary = true
     46 	return fdb
     47 }
     48 
     49 // String implements the string interface.
     50 func (fdb *fsDb) String() string {
     51 	return "fsdb: " + fdb.dir
     52 }
     53 
     54 // Connect implements the Db interface.
     55 func (fdb *fsDb) Connect(ctx context.Context, connStr string) error {
     56 	if fdb.dir != "" {
     57 		logg.WarnCtxf(ctx, "already connected", "conn", fdb.dir)
     58 		return nil
     59 	}
     60 	err := os.MkdirAll(connStr, 0700)
     61 	if err != nil {
     62 		return err
     63 	}
     64 	fdb.DbBase.Connect(ctx, connStr)
     65 	fdb.dir = fdb.Connection()
     66 	return nil
     67 }
     68 
     69 // ToKey overrides the BaseDb implementation, creating a base64 string
     70 // if binary keys have been enabled
     71 func (fdb *fsDb) ToKey(ctx context.Context, key []byte) (db.LookupKey, error) {
     72 	if fdb.binary {
     73 		s := base64.StdEncoding.EncodeToString(key)
     74 		key = []byte(s)
     75 	}
     76 	return fdb.DbBase.ToKey(ctx, key)
     77 }
     78 
     79 func (fdb *fsDb) DecodeKey(ctx context.Context, key []byte) ([]byte, error) {
     80 	key, err := fdb.DbBase.DecodeKey(ctx, key)
     81 	if err != nil {
     82 		return nil, err
     83 	}
     84 	if !fdb.binary {
     85 		return key, nil
     86 	}
     87 	oldKey := key
     88 	key, err = base64.StdEncoding.DecodeString(string(key))
     89 	if err != nil {
     90 		return []byte{}, fmt.Errorf("base64 decode error '%s': %v", oldKey, err)
     91 	}
     92 	logg.TraceCtxf(ctx, "decoding base64 key", "base64", oldKey, "bin", key)
     93 	return key, nil
     94 }
     95 
     96 // Get implements the Db interface.
     97 func (fdb *fsDb) Get(ctx context.Context, key []byte) ([]byte, error) {
     98 	var f *os.File
     99 	lk, err := fdb.ToKey(ctx, key)
    100 	if err != nil {
    101 		return nil, err
    102 	}
    103 	flk, err := fdb.pathFor(ctx, &lk)
    104 	if err != nil {
    105 		return nil, err
    106 	}
    107 	flka, err := fdb.altPathFor(ctx, &lk)
    108 	if err != nil {
    109 		return nil, err
    110 	}
    111 	for i, fp := range []string{flk.Translation, flka.Translation, flk.Default, flka.Default} {
    112 		if fp == "" {
    113 			logg.TraceCtxf(ctx, "fs get skip missing", "i", i)
    114 			continue
    115 		}
    116 		logg.TraceCtxf(ctx, "trying fs get", "i", i, "key", key, "path", fp)
    117 		f, err = os.Open(fp)
    118 		if err == nil {
    119 			break
    120 		}
    121 		if !errors.Is(err, fs.ErrNotExist) {
    122 			return nil, err
    123 		}
    124 	}
    125 	if f == nil {
    126 		return nil, db.NewErrNotFound(key)
    127 	}
    128 	defer f.Close()
    129 	b, err := ioutil.ReadAll(f)
    130 	if err != nil {
    131 		return nil, err
    132 	}
    133 	return b, nil
    134 }
    135 
    136 // Put implements the Db interface.
    137 func (fdb *fsDb) Put(ctx context.Context, key []byte, val []byte) error {
    138 	if !fdb.CheckPut() {
    139 		return errors.New("unsafe put and safety set")
    140 	}
    141 	lk, err := fdb.ToKey(ctx, key)
    142 	if err != nil {
    143 		return err
    144 	}
    145 	flk, err := fdb.pathFor(ctx, &lk)
    146 	if err != nil {
    147 		return err
    148 	}
    149 	logg.TraceCtxf(ctx, "fs put", "key", key, "lk", lk, "flk", flk, "val", val)
    150 	if flk.Translation != "" {
    151 		err = ioutil.WriteFile(flk.Translation, val, 0600)
    152 		if err != nil {
    153 			return err
    154 		}
    155 		return nil
    156 	}
    157 	return ioutil.WriteFile(flk.Default, val, 0600)
    158 }
    159 
    160 // Close implements the Db interface.
    161 func (fdb *fsDb) Close(ctx context.Context) error {
    162 	return nil
    163 }
    164 
    165 // create a key safe for the filesystem.
    166 func (fdb *fsDb) pathFor(ctx context.Context, lk *db.LookupKey) (fsLookupKey, error) {
    167 	var flk fsLookupKey
    168 	lk.Default[0] += 0x30
    169 	flk.Default = path.Join(fdb.dir, string(lk.Default))
    170 	if lk.Translation != nil {
    171 		lk.Translation[0] += 0x30
    172 		flk.Translation = path.Join(fdb.dir, string(lk.Translation))
    173 	}
    174 	return flk, nil
    175 }
    176 
    177 // create a key safe for the filesystem, matching legacy resource.FsResource name.
    178 func (fdb *fsDb) altPathFor(ctx context.Context, lk *db.LookupKey) (fsLookupKey, error) {
    179 	var flk fsLookupKey
    180 	fb := string(lk.Default[1:])
    181 	if fdb.Prefix() == db.DATATYPE_BIN {
    182 		fb += ".bin"
    183 	}
    184 	flk.Default = path.Join(fdb.dir, fb)
    185 
    186 	if lk.Translation != nil {
    187 		fb = string(lk.Translation[1:])
    188 		if fdb.Prefix() == db.DATATYPE_BIN {
    189 			fb += ".bin"
    190 		}
    191 		flk.Translation = path.Join(fdb.dir, fb)
    192 	}
    193 
    194 	return flk, nil
    195 }