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