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.