github.com/olivere/camlistore@v0.0.0-20140121221811-1b7ac2da0199/server/appengine/camli/storage.go (about) 1 // +build appengine 2 3 /* 4 Copyright 2011 Google Inc. 5 6 Licensed under the Apache License, Version 2.0 (the "License"); 7 you may not use this file except in compliance with the License. 8 You may obtain a copy of the License at 9 10 http://www.apache.org/licenses/LICENSE-2.0 11 12 Unless required by applicable law or agreed to in writing, software 13 distributed under the License is distributed on an "AS IS" BASIS, 14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 See the License for the specific language governing permissions and 16 limitations under the License. 17 */ 18 19 package appengine 20 21 import ( 22 "bytes" 23 "fmt" 24 "io" 25 "io/ioutil" 26 "os" 27 "strings" 28 "sync" 29 30 "appengine" 31 "appengine/blobstore" 32 "appengine/datastore" 33 34 "camlistore.org/pkg/blob" 35 "camlistore.org/pkg/blobserver" 36 "camlistore.org/pkg/context" 37 "camlistore.org/pkg/jsonconfig" 38 ) 39 40 const ( 41 blobKind = "Blob" 42 memKind = "NsBlobMember" // blob membership in a namespace 43 ) 44 45 var _ blobserver.Storage = (*appengineStorage)(nil) 46 47 type appengineStorage struct { 48 namespace string // never empty; config initializes to at least "-" 49 } 50 51 // blobEnt is stored once per unique blob, keyed by blobref. 52 type blobEnt struct { 53 Size int64 `datastore:"Size,noindex"` 54 BlobKey appengine.BlobKey `datastore:"BlobKey,noindex"` 55 Namespaces string `datastore:"Namespaces,noindex"` // |-separated string of namespaces 56 57 // TODO(bradfitz): IsCamliSchemaBlob bool? ... probably want 58 // on enumeration (memEnt) too. 59 } 60 61 // memEnt is stored once per blob in a namespace, keyed by "ns|blobref" 62 type memEnt struct { 63 Size int64 `datastore:"Size,noindex"` 64 } 65 66 func byteDecSize(b []byte) (int64, error) { 67 var size int64 68 n, err := fmt.Fscanf(bytes.NewBuffer(b), "%d", &size) 69 if n != 1 || err != nil { 70 return 0, fmt.Errorf("invalid Size column in datastore: %q", string(b)) 71 } 72 return size, nil 73 } 74 75 func (b *blobEnt) inNamespace(ns string) (out bool) { 76 for _, in := range strings.Split(b.Namespaces, "|") { 77 if ns == in { 78 return true 79 } 80 } 81 return false 82 } 83 84 func entKey(c appengine.Context, br blob.Ref) *datastore.Key { 85 return datastore.NewKey(c, blobKind, br.String(), 0, nil) 86 } 87 88 func (s *appengineStorage) memKey(c appengine.Context, br blob.Ref) *datastore.Key { 89 return datastore.NewKey(c, memKind, fmt.Sprintf("%s|%s", s.namespace, br.String()), 0, nil) 90 } 91 92 func fetchEnt(c appengine.Context, br blob.Ref) (*blobEnt, error) { 93 row := new(blobEnt) 94 err := datastore.Get(c, entKey(c, br), row) 95 if err != nil { 96 return nil, err 97 } 98 return row, nil 99 } 100 101 func newFromConfig(ld blobserver.Loader, config jsonconfig.Obj) (storage blobserver.Storage, err error) { 102 sto := &appengineStorage{ 103 namespace: config.OptionalString("namespace", ""), 104 } 105 if err := config.Validate(); err != nil { 106 return nil, err 107 } 108 sto.namespace, err = sanitizeNamespace(sto.namespace) 109 if err != nil { 110 return nil, err 111 } 112 return sto, nil 113 } 114 115 var dummyCloser = ioutil.NopCloser(strings.NewReader("")) 116 117 func (sto *appengineStorage) FetchStreaming(br blob.Ref) (file io.ReadCloser, size int64, err error) { 118 loan := ctxPool.Get() 119 ctx := loan 120 defer func() { 121 if loan != nil { 122 loan.Return() 123 } 124 }() 125 126 row, err := fetchEnt(ctx, br) 127 if err == datastore.ErrNoSuchEntity { 128 err = os.ErrNotExist 129 return 130 } 131 if err != nil { 132 return 133 } 134 if !row.inNamespace(sto.namespace) { 135 err = os.ErrNotExist 136 return 137 } 138 139 closeLoan := loan 140 var c io.Closer = &onceCloser{fn: func() { closeLoan.Return() }} 141 loan = nil // take it, so it's not defer-closed 142 143 reader := blobstore.NewReader(ctx, appengine.BlobKey(string(row.BlobKey))) 144 type readCloser struct { 145 io.Reader 146 io.Closer 147 } 148 return readCloser{reader, c}, row.Size, nil 149 } 150 151 type onceCloser struct { 152 once sync.Once 153 fn func() 154 } 155 156 func (oc *onceCloser) Close() error { 157 oc.once.Do(oc.fn) 158 return nil 159 } 160 161 var crossGroupTransaction = &datastore.TransactionOptions{XG: true} 162 163 func (sto *appengineStorage) ReceiveBlob(br blob.Ref, in io.Reader) (sb blob.SizedRef, err error) { 164 loan := ctxPool.Get() 165 defer loan.Return() 166 ctx := loan 167 168 var b bytes.Buffer 169 written, err := io.Copy(&b, in) 170 if err != nil { 171 return 172 } 173 174 // bkey is non-empty once we've uploaded the blob. 175 var bkey appengine.BlobKey 176 177 // uploadBlob uploads the blob, unless it's already been done. 178 uploadBlob := func(ctx appengine.Context) error { 179 if len(bkey) > 0 { 180 return nil // already done in previous transaction attempt 181 } 182 bw, err := blobstore.Create(ctx, "application/octet-stream") 183 if err != nil { 184 return err 185 } 186 _, err = io.Copy(bw, &b) 187 if err != nil { 188 // TODO(bradfitz): try to clean up; close it, see if we can find the key, delete it. 189 ctx.Errorf("blobstore Copy error: %v", err) 190 return err 191 } 192 err = bw.Close() 193 if err != nil { 194 // TODO(bradfitz): try to clean up; see if we can find the key, delete it. 195 ctx.Errorf("blobstore Close error: %v", err) 196 return err 197 } 198 k, err := bw.Key() 199 if err == nil { 200 bkey = k 201 } 202 return err 203 } 204 205 tryFunc := func(tc appengine.Context) error { 206 row, err := fetchEnt(tc, br) 207 switch err { 208 case datastore.ErrNoSuchEntity: 209 if err := uploadBlob(tc); err != nil { 210 tc.Errorf("uploadBlob failed: %v", err) 211 return err 212 } 213 row = &blobEnt{ 214 Size: written, 215 BlobKey: bkey, 216 Namespaces: sto.namespace, 217 } 218 _, err = datastore.Put(tc, entKey(tc, br), row) 219 if err != nil { 220 return err 221 } 222 case nil: 223 if row.inNamespace(sto.namespace) { 224 // Nothing to do 225 return nil 226 } 227 row.Namespaces = row.Namespaces + "|" + sto.namespace 228 _, err = datastore.Put(tc, entKey(tc, br), row) 229 if err != nil { 230 return err 231 } 232 default: 233 return err 234 } 235 236 // Add membership row 237 _, err = datastore.Put(tc, sto.memKey(tc, br), &memEnt{ 238 Size: written, 239 }) 240 return err 241 } 242 err = datastore.RunInTransaction(ctx, tryFunc, crossGroupTransaction) 243 if err != nil { 244 if len(bkey) > 0 { 245 // If we just created this blob but we 246 // ultimately failed, try our best to delete 247 // it so it's not orphaned. 248 blobstore.Delete(ctx, bkey) 249 } 250 return 251 } 252 return blob.SizedRef{br, written}, nil 253 } 254 255 // NOTE(bslatkin): No fucking clue if this works. 256 func (sto *appengineStorage) RemoveBlobs(blobs []blob.Ref) error { 257 loan := ctxPool.Get() 258 defer loan.Return() 259 ctx := loan 260 261 tryFunc := func(tc appengine.Context, br blob.Ref) error { 262 // TODO(bslatkin): Make the DB gets in this a multi-get. 263 // Remove the namespace from the blobEnt 264 row, err := fetchEnt(tc, br) 265 switch err { 266 case datastore.ErrNoSuchEntity: 267 // Doesn't exist, that means there should be no memEnt, but let's be 268 // paranoid and double check anyways. 269 case nil: 270 // blobEnt exists, remove our namespace from it if possible. 271 newNS := []string{} 272 for _, val := range strings.Split(string(row.Namespaces), "|") { 273 if val != sto.namespace { 274 newNS = append(newNS, val) 275 } 276 } 277 if v := strings.Join(newNS, "|"); v != row.Namespaces { 278 row.Namespaces = v 279 _, err = datastore.Put(tc, entKey(tc, br), row) 280 if err != nil { 281 return err 282 } 283 } 284 default: 285 return err 286 } 287 288 // Blindly delete the memEnt. 289 err = datastore.Delete(tc, sto.memKey(tc, br)) 290 return err 291 } 292 293 for _, br := range blobs { 294 ret := datastore.RunInTransaction( 295 ctx, 296 func(tc appengine.Context) error { 297 return tryFunc(tc, br) 298 }, 299 crossGroupTransaction) 300 if ret != nil { 301 return ret 302 } 303 } 304 return nil 305 } 306 307 func (sto *appengineStorage) StatBlobs(dest chan<- blob.SizedRef, blobs []blob.Ref) error { 308 loan := ctxPool.Get() 309 defer loan.Return() 310 ctx := loan 311 312 var ( 313 keys = make([]*datastore.Key, 0, len(blobs)) 314 out = make([]interface{}, 0, len(blobs)) 315 errs = make([]error, len(blobs)) 316 ) 317 for _, br := range blobs { 318 keys = append(keys, sto.memKey(ctx, br)) 319 out = append(out, new(memEnt)) 320 } 321 err := datastore.GetMulti(ctx, keys, out) 322 if merr, ok := err.(appengine.MultiError); ok { 323 errs = []error(merr) 324 err = nil 325 } 326 if err != nil { 327 return err 328 } 329 for i, br := range blobs { 330 thisErr := errs[i] 331 if thisErr == datastore.ErrNoSuchEntity { 332 continue 333 } 334 if thisErr != nil { 335 err = errs[i] // just return last one found? 336 continue 337 } 338 ent := out[i].(*memEnt) 339 dest <- blob.SizedRef{br, ent.Size} 340 } 341 return err 342 } 343 344 func (sto *appengineStorage) EnumerateBlobs(ctx *context.Context, dest chan<- blob.SizedRef, after string, limit int) error { 345 defer close(dest) 346 347 loan := ctxPool.Get() 348 defer loan.Return() 349 actx := loan 350 351 prefix := sto.namespace + "|" 352 keyBegin := datastore.NewKey(actx, memKind, prefix+after, 0, nil) 353 keyEnd := datastore.NewKey(actx, memKind, sto.namespace+"~", 0, nil) 354 355 q := datastore.NewQuery(memKind).Limit(int(limit)).Filter("__key__>", keyBegin).Filter("__key__<", keyEnd) 356 it := q.Run(actx) 357 var row memEnt 358 for { 359 key, err := it.Next(&row) 360 if err == datastore.Done { 361 break 362 } 363 if err != nil { 364 return err 365 } 366 select { 367 case dest <- blob.SizedRef{blob.ParseOrZero(key.StringID()[len(prefix):]), row.Size}: 368 case <-ctx.Done(): 369 return context.ErrCanceled 370 } 371 } 372 return nil 373 } 374 375 // TODO(bslatkin): sync does not work on App Engine yet because there are no 376 // background threads to do the sync loop. The plan is to break the 377 // syncer code up into two parts: 1) accepts notifications of new blobs to 378 // sync, 2) does one unit of work enumerating recent blobs and syncing them. 379 // In App Engine land, 1) will result in a task to be enqueued, and 2) will 380 // be called from within that queue context.