github.com/slspeek/camlistore_namedsearch@v0.0.0-20140519202248-ed6f70f7721a/pkg/blobserver/cond/cond.go (about) 1 /* 2 Copyright 2011 Google Inc. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 /* 18 Package cond registers the "cond" conditional blobserver storage type 19 to select routing of get/put operations on blobs to other storage 20 targets as a function of their content. 21 22 Currently only the "isSchema" predicate is defined. 23 24 Example usage: 25 26 "/bs-and-maybe-also-index/": { 27 "handler": "storage-cond", 28 "handlerArgs": { 29 "write": { 30 "if": "isSchema", 31 "then": "/bs-and-index/", 32 "else": "/bs/" 33 }, 34 "read": "/bs/" 35 } 36 } 37 */ 38 package cond 39 40 import ( 41 "bytes" 42 "errors" 43 "fmt" 44 "io" 45 "net/http" 46 "time" 47 48 "camlistore.org/pkg/blob" 49 "camlistore.org/pkg/blobserver" 50 "camlistore.org/pkg/context" 51 "camlistore.org/pkg/jsonconfig" 52 "camlistore.org/pkg/schema" 53 ) 54 55 const buffered = 8 56 57 // A storageFunc selects a destination for a given blob. It may consume from src but must always return 58 // a newSrc that is identical to the original src passed in. 59 type storageFunc func(br blob.Ref, src io.Reader) (dest blobserver.Storage, newSrc io.Reader, err error) 60 61 type condStorage struct { 62 storageForReceive storageFunc 63 read blobserver.Storage 64 remove blobserver.Storage 65 66 ctx *http.Request // optional per-request context 67 } 68 69 func (sto *condStorage) StorageGeneration() (initTime time.Time, random string, err error) { 70 if gener, ok := sto.read.(blobserver.Generationer); ok { 71 return gener.StorageGeneration() 72 } 73 err = blobserver.GenerationNotSupportedError(fmt.Sprintf("blobserver.Generationer not implemented on %T", sto.read)) 74 return 75 } 76 77 func (sto *condStorage) ResetStorageGeneration() error { 78 if gener, ok := sto.read.(blobserver.Generationer); ok { 79 return gener.ResetStorageGeneration() 80 } 81 return blobserver.GenerationNotSupportedError(fmt.Sprintf("blobserver.Generationer not implemented on %T", sto.read)) 82 } 83 84 func newFromConfig(ld blobserver.Loader, conf jsonconfig.Obj) (storage blobserver.Storage, err error) { 85 sto := &condStorage{} 86 87 receive := conf.OptionalStringOrObject("write") 88 read := conf.RequiredString("read") 89 remove := conf.OptionalString("remove", "") 90 if err := conf.Validate(); err != nil { 91 return nil, err 92 } 93 94 if receive != nil { 95 sto.storageForReceive, err = buildStorageForReceive(ld, receive) 96 if err != nil { 97 return 98 } 99 } 100 101 sto.read, err = ld.GetStorage(read) 102 if err != nil { 103 return 104 } 105 106 if remove != "" { 107 sto.remove, err = ld.GetStorage(remove) 108 if err != nil { 109 return 110 } 111 } 112 return sto, nil 113 } 114 115 func buildStorageForReceive(ld blobserver.Loader, confOrString interface{}) (storageFunc, error) { 116 // Static configuration from a string 117 if s, ok := confOrString.(string); ok { 118 sto, err := ld.GetStorage(s) 119 if err != nil { 120 return nil, err 121 } 122 f := func(br blob.Ref, src io.Reader) (blobserver.Storage, io.Reader, error) { 123 return sto, src, nil 124 } 125 return f, nil 126 } 127 128 conf := jsonconfig.Obj(confOrString.(map[string]interface{})) 129 130 ifStr := conf.RequiredString("if") 131 // TODO: let 'then' and 'else' point to not just strings but either 132 // a string or a JSON object with another condition, and then 133 // call buildStorageForReceive on it recursively 134 thenTarget := conf.RequiredString("then") 135 elseTarget := conf.RequiredString("else") 136 if err := conf.Validate(); err != nil { 137 return nil, err 138 } 139 thenSto, err := ld.GetStorage(thenTarget) 140 if err != nil { 141 return nil, err 142 } 143 elseSto, err := ld.GetStorage(elseTarget) 144 if err != nil { 145 return nil, err 146 } 147 148 switch ifStr { 149 case "isSchema": 150 return isSchemaPicker(thenSto, elseSto), nil 151 } 152 return nil, fmt.Errorf("cond: unsupported 'if' type of %q", ifStr) 153 } 154 155 func isSchemaPicker(thenSto, elseSto blobserver.Storage) storageFunc { 156 return func(br blob.Ref, src io.Reader) (dest blobserver.Storage, newSrc io.Reader, err error) { 157 var buf bytes.Buffer 158 blob, err := schema.BlobFromReader(br, io.TeeReader(src, &buf)) 159 newSrc = io.MultiReader(bytes.NewReader(buf.Bytes()), src) 160 if err != nil || blob.Type() == "" { 161 return elseSto, newSrc, nil 162 } 163 return thenSto, newSrc, nil 164 } 165 } 166 167 func (sto *condStorage) ReceiveBlob(br blob.Ref, src io.Reader) (sb blob.SizedRef, err error) { 168 destSto, src, err := sto.storageForReceive(br, src) 169 if err != nil { 170 return 171 } 172 return blobserver.Receive(destSto, br, src) 173 } 174 175 func (sto *condStorage) RemoveBlobs(blobs []blob.Ref) error { 176 if sto.remove != nil { 177 return sto.remove.RemoveBlobs(blobs) 178 } 179 return errors.New("cond: Remove not configured") 180 } 181 182 func (sto *condStorage) Fetch(b blob.Ref) (file io.ReadCloser, size uint32, err error) { 183 if sto.read != nil { 184 return sto.read.Fetch(b) 185 } 186 err = errors.New("cond: Read not configured") 187 return 188 } 189 190 func (sto *condStorage) StatBlobs(dest chan<- blob.SizedRef, blobs []blob.Ref) error { 191 if sto.read != nil { 192 return sto.read.StatBlobs(dest, blobs) 193 } 194 return errors.New("cond: Read not configured") 195 } 196 197 func (sto *condStorage) EnumerateBlobs(ctx *context.Context, dest chan<- blob.SizedRef, after string, limit int) error { 198 if sto.read != nil { 199 return sto.read.EnumerateBlobs(ctx, dest, after, limit) 200 } 201 return errors.New("cond: Read not configured") 202 } 203 204 func init() { 205 blobserver.RegisterStorageConstructor("cond", blobserver.StorageConstructor(newFromConfig)) 206 }