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 }