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 }