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