github.com/creachadair/ffs@v0.17.3/storage/encoded/encoded.go (about) 1 // Copyright 2019 Michael J. Fromberger. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package encoded implements a [blob.StoreCloser] that applies a reversible 16 // encoding such as compression or encryption to the data. 17 package encoded 18 19 import ( 20 "bytes" 21 "context" 22 "io" 23 "iter" 24 25 "github.com/creachadair/ffs/blob" 26 ) 27 28 // A Codec defines the capabilities needed to encode and decode. 29 type Codec interface { 30 // Encode writes the encoding of src to w. After encoding, src may be garbage. 31 Encode(w io.Writer, src []byte) error 32 33 // Decode writes the decoding of src to w. After decoding, src may be garbage. 34 Decode(w io.Writer, src []byte) error 35 } 36 37 // A Store wraps an existing [blob.Store] implementation so that its key spaces 38 // are encoded using a [Codec]. 39 type Store struct { 40 codec Codec 41 real blob.Store 42 } 43 44 // KV implements a method of [blob.Store]. The concrete type of keyspaces 45 // returned is [KV]. 46 func (s Store) KV(ctx context.Context, name string) (blob.KV, error) { 47 kv, err := s.real.KV(ctx, name) 48 if err != nil { 49 return nil, err 50 } 51 return KV{codec: s.codec, real: kv}, nil 52 } 53 54 // CAS implements a method of [blob.Store]. 55 func (s Store) CAS(ctx context.Context, name string) (blob.CAS, error) { 56 return blob.CASFromKVError(s.KV(ctx, name)) 57 } 58 59 // Sub implements a method of [blob.Store]. The concrete type of stores 60 // returned is [Store]. 61 func (s Store) Sub(ctx context.Context, name string) (blob.Store, error) { 62 sub, err := s.real.Sub(ctx, name) 63 if err != nil { 64 return nil, err 65 } 66 return Store{codec: s.codec, real: sub}, nil 67 } 68 69 // Close implements a method of the [blob.StoreCloser] interface. 70 func (s Store) Close(ctx context.Context) error { 71 if c, ok := s.real.(blob.Closer); ok { 72 return c.Close(ctx) 73 } 74 return nil 75 } 76 77 // New constructs a new store that delegates to s and uses c to encode and 78 // decode blob data. New will panic if either s or c is nil. 79 func New(s blob.Store, c Codec) Store { 80 if s == nil { 81 panic("store is nil") 82 } else if c == nil { 83 panic("codec is nil") 84 } 85 return Store{codec: c, real: s} 86 } 87 88 // A KV wraps an existing [blob.KV] implementation in which blobs are encoded 89 // using a [Codec]. 90 type KV struct { 91 codec Codec // used to compress and decompress blobs 92 real blob.KV // the underlying storage implementation 93 } 94 95 // NewKV constructs a new KV that delegates to kv and uses c to encode and 96 // decode blob data. NewKV will panic if either kv or c is nil. 97 func NewKV(kv blob.KV, c Codec) KV { 98 if kv == nil { 99 panic("keyspace is nil") 100 } else if c == nil { 101 panic("codec is nil") 102 } 103 return KV{codec: c, real: kv} 104 } 105 106 // Get implements part of the [blob.KV] interface. 107 func (s KV) Get(ctx context.Context, key string) ([]byte, error) { 108 enc, err := s.real.Get(ctx, key) 109 if err != nil { 110 return nil, err 111 } 112 113 // Ideally we would request the decoded length and use that to allocate a 114 // buffer for the output. For some codecs, however, it isn't possible to 115 // compute the decoded length without performing the decoding, which loses 116 // the benefit. 117 var buf bytes.Buffer 118 if err := s.codec.Decode(&buf, enc); err != nil { 119 return nil, err 120 } 121 return buf.Bytes(), nil 122 } 123 124 // Has implements part of the [blob.KV] interface. 125 func (s KV) Has(ctx context.Context, keys ...string) (blob.KeySet, error) { 126 return s.real.Has(ctx, keys...) 127 } 128 129 // Put implements part of the [blob.KV] interface. 130 func (s KV) Put(ctx context.Context, opts blob.PutOptions) error { 131 buf := bytes.NewBuffer(make([]byte, 0, len(opts.Data))) 132 if err := s.codec.Encode(buf, opts.Data); err != nil { 133 return err 134 } 135 // Leave the original options as given, but replace the data. 136 opts.Data = buf.Bytes() 137 return s.real.Put(ctx, opts) 138 } 139 140 // Delete implements part of the [blob.KV] interface. 141 // It delegates directly to the underlying store. 142 func (s KV) Delete(ctx context.Context, key string) error { 143 return s.real.Delete(ctx, key) 144 } 145 146 // List implements part of the [blob.KV] interface. 147 // It delegates directly to the underlying store. 148 func (s KV) List(ctx context.Context, start string) iter.Seq2[string, error] { 149 return s.real.List(ctx, start) 150 } 151 152 // Len implements part of the [blob.KV] interface. 153 // It delegates directly to the underlying store. 154 func (s KV) Len(ctx context.Context) (int64, error) { return s.real.Len(ctx) }