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.