github.com/fawick/restic@v0.1.1-0.20171126184616-c02923fbfc79/internal/backend/b2/b2.go (about)

     1  package b2
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"net/http"
     7  	"path"
     8  	"strings"
     9  
    10  	"github.com/restic/restic/internal/backend"
    11  	"github.com/restic/restic/internal/debug"
    12  	"github.com/restic/restic/internal/errors"
    13  	"github.com/restic/restic/internal/restic"
    14  
    15  	"github.com/kurin/blazer/b2"
    16  )
    17  
    18  // b2Backend is a backend which stores its data on Backblaze B2.
    19  type b2Backend struct {
    20  	client       *b2.Client
    21  	bucket       *b2.Bucket
    22  	cfg          Config
    23  	listMaxItems int
    24  	backend.Layout
    25  	sem *backend.Semaphore
    26  }
    27  
    28  const defaultListMaxItems = 1000
    29  
    30  // ensure statically that *b2Backend implements restic.Backend.
    31  var _ restic.Backend = &b2Backend{}
    32  
    33  func newClient(ctx context.Context, cfg Config, rt http.RoundTripper) (*b2.Client, error) {
    34  	opts := []b2.ClientOption{b2.Transport(rt)}
    35  
    36  	c, err := b2.NewClient(ctx, cfg.AccountID, cfg.Key, opts...)
    37  	if err != nil {
    38  		return nil, errors.Wrap(err, "b2.NewClient")
    39  	}
    40  	return c, nil
    41  }
    42  
    43  // Open opens a connection to the B2 service.
    44  func Open(cfg Config, rt http.RoundTripper) (restic.Backend, error) {
    45  	debug.Log("cfg %#v", cfg)
    46  
    47  	ctx, cancel := context.WithCancel(context.TODO())
    48  	defer cancel()
    49  
    50  	client, err := newClient(ctx, cfg, rt)
    51  	if err != nil {
    52  		return nil, err
    53  	}
    54  
    55  	bucket, err := client.Bucket(ctx, cfg.Bucket)
    56  	if err != nil {
    57  		return nil, errors.Wrap(err, "Bucket")
    58  	}
    59  
    60  	sem, err := backend.NewSemaphore(cfg.Connections)
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  
    65  	be := &b2Backend{
    66  		client: client,
    67  		bucket: bucket,
    68  		cfg:    cfg,
    69  		Layout: &backend.DefaultLayout{
    70  			Join: path.Join,
    71  			Path: cfg.Prefix,
    72  		},
    73  		listMaxItems: defaultListMaxItems,
    74  		sem:          sem,
    75  	}
    76  
    77  	return be, nil
    78  }
    79  
    80  // Create opens a connection to the B2 service. If the bucket does not exist yet,
    81  // it is created.
    82  func Create(cfg Config, rt http.RoundTripper) (restic.Backend, error) {
    83  	debug.Log("cfg %#v", cfg)
    84  
    85  	ctx, cancel := context.WithCancel(context.TODO())
    86  	defer cancel()
    87  
    88  	client, err := newClient(ctx, cfg, rt)
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  
    93  	attr := b2.BucketAttrs{
    94  		Type: b2.Private,
    95  	}
    96  	bucket, err := client.NewBucket(ctx, cfg.Bucket, &attr)
    97  	if err != nil {
    98  		return nil, errors.Wrap(err, "NewBucket")
    99  	}
   100  
   101  	sem, err := backend.NewSemaphore(cfg.Connections)
   102  	if err != nil {
   103  		return nil, err
   104  	}
   105  
   106  	be := &b2Backend{
   107  		client: client,
   108  		bucket: bucket,
   109  		cfg:    cfg,
   110  		Layout: &backend.DefaultLayout{
   111  			Join: path.Join,
   112  			Path: cfg.Prefix,
   113  		},
   114  		listMaxItems: defaultListMaxItems,
   115  		sem:          sem,
   116  	}
   117  
   118  	present, err := be.Test(context.TODO(), restic.Handle{Type: restic.ConfigFile})
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  
   123  	if present {
   124  		return nil, errors.New("config already exists")
   125  	}
   126  
   127  	return be, nil
   128  }
   129  
   130  // SetListMaxItems sets the number of list items to load per request.
   131  func (be *b2Backend) SetListMaxItems(i int) {
   132  	be.listMaxItems = i
   133  }
   134  
   135  // Location returns the location for the backend.
   136  func (be *b2Backend) Location() string {
   137  	return be.cfg.Bucket
   138  }
   139  
   140  // IsNotExist returns true if the error is caused by a non-existing file.
   141  func (be *b2Backend) IsNotExist(err error) bool {
   142  	return b2.IsNotExist(errors.Cause(err))
   143  }
   144  
   145  // Load returns the data stored in the backend for h at the given offset
   146  // and saves it in p. Load has the same semantics as io.ReaderAt.
   147  func (be *b2Backend) Load(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) {
   148  	debug.Log("Load %v, length %v, offset %v from %v", h, length, offset, be.Filename(h))
   149  	if err := h.Valid(); err != nil {
   150  		return nil, err
   151  	}
   152  
   153  	if offset < 0 {
   154  		return nil, errors.New("offset is negative")
   155  	}
   156  
   157  	if length < 0 {
   158  		return nil, errors.Errorf("invalid length %d", length)
   159  	}
   160  
   161  	ctx, cancel := context.WithCancel(ctx)
   162  
   163  	be.sem.GetToken()
   164  
   165  	name := be.Layout.Filename(h)
   166  	obj := be.bucket.Object(name)
   167  
   168  	if offset == 0 && length == 0 {
   169  		rd := obj.NewReader(ctx)
   170  		return be.sem.ReleaseTokenOnClose(rd, cancel), nil
   171  	}
   172  
   173  	// pass a negative length to NewRangeReader so that the remainder of the
   174  	// file is read.
   175  	if length == 0 {
   176  		length = -1
   177  	}
   178  
   179  	rd := obj.NewRangeReader(ctx, offset, int64(length))
   180  	return be.sem.ReleaseTokenOnClose(rd, cancel), nil
   181  }
   182  
   183  // Save stores data in the backend at the handle.
   184  func (be *b2Backend) Save(ctx context.Context, h restic.Handle, rd io.Reader) error {
   185  	ctx, cancel := context.WithCancel(ctx)
   186  	defer cancel()
   187  
   188  	if err := h.Valid(); err != nil {
   189  		return err
   190  	}
   191  
   192  	be.sem.GetToken()
   193  	defer be.sem.ReleaseToken()
   194  
   195  	name := be.Filename(h)
   196  	debug.Log("Save %v, name %v", h, name)
   197  	obj := be.bucket.Object(name)
   198  
   199  	_, err := obj.Attrs(ctx)
   200  	if err == nil {
   201  		debug.Log("  %v already exists", h)
   202  		return errors.New("key already exists")
   203  	}
   204  
   205  	w := obj.NewWriter(ctx)
   206  	n, err := io.Copy(w, rd)
   207  	debug.Log("  saved %d bytes, err %v", n, err)
   208  
   209  	if err != nil {
   210  		_ = w.Close()
   211  		return errors.Wrap(err, "Copy")
   212  	}
   213  
   214  	return errors.Wrap(w.Close(), "Close")
   215  }
   216  
   217  // Stat returns information about a blob.
   218  func (be *b2Backend) Stat(ctx context.Context, h restic.Handle) (bi restic.FileInfo, err error) {
   219  	debug.Log("Stat %v", h)
   220  
   221  	be.sem.GetToken()
   222  	defer be.sem.ReleaseToken()
   223  
   224  	name := be.Filename(h)
   225  	obj := be.bucket.Object(name)
   226  	info, err := obj.Attrs(ctx)
   227  	if err != nil {
   228  		debug.Log("Attrs() err %v", err)
   229  		return restic.FileInfo{}, errors.Wrap(err, "Stat")
   230  	}
   231  	return restic.FileInfo{Size: info.Size}, nil
   232  }
   233  
   234  // Test returns true if a blob of the given type and name exists in the backend.
   235  func (be *b2Backend) Test(ctx context.Context, h restic.Handle) (bool, error) {
   236  	debug.Log("Test %v", h)
   237  
   238  	be.sem.GetToken()
   239  	defer be.sem.ReleaseToken()
   240  
   241  	found := false
   242  	name := be.Filename(h)
   243  	obj := be.bucket.Object(name)
   244  	info, err := obj.Attrs(ctx)
   245  	if err == nil && info != nil && info.Status == b2.Uploaded {
   246  		found = true
   247  	}
   248  	return found, nil
   249  }
   250  
   251  // Remove removes the blob with the given name and type.
   252  func (be *b2Backend) Remove(ctx context.Context, h restic.Handle) error {
   253  	debug.Log("Remove %v", h)
   254  
   255  	be.sem.GetToken()
   256  	defer be.sem.ReleaseToken()
   257  
   258  	obj := be.bucket.Object(be.Filename(h))
   259  	return errors.Wrap(obj.Delete(ctx), "Delete")
   260  }
   261  
   262  // List returns a channel that yields all names of blobs of type t. A
   263  // goroutine is started for this. If the channel done is closed, sending
   264  // stops.
   265  func (be *b2Backend) List(ctx context.Context, t restic.FileType) <-chan string {
   266  	debug.Log("List %v", t)
   267  	ch := make(chan string)
   268  
   269  	ctx, cancel := context.WithCancel(ctx)
   270  
   271  	go func() {
   272  		defer close(ch)
   273  		defer cancel()
   274  
   275  		prefix := be.Dirname(restic.Handle{Type: t})
   276  		cur := &b2.Cursor{Prefix: prefix}
   277  
   278  		for {
   279  			be.sem.GetToken()
   280  			objs, c, err := be.bucket.ListCurrentObjects(ctx, be.listMaxItems, cur)
   281  			be.sem.ReleaseToken()
   282  			if err != nil && err != io.EOF {
   283  				// TODO: return err to caller once err handling in List() is improved
   284  				debug.Log("List: %v", err)
   285  				return
   286  			}
   287  			debug.Log("returned %v items", len(objs))
   288  			for _, obj := range objs {
   289  				// Skip objects returned that do not have the specified prefix.
   290  				if !strings.HasPrefix(obj.Name(), prefix) {
   291  					continue
   292  				}
   293  
   294  				m := path.Base(obj.Name())
   295  				if m == "" {
   296  					continue
   297  				}
   298  
   299  				select {
   300  				case ch <- m:
   301  				case <-ctx.Done():
   302  					return
   303  				}
   304  			}
   305  			if err == io.EOF {
   306  				return
   307  			}
   308  			cur = c
   309  		}
   310  	}()
   311  
   312  	return ch
   313  }
   314  
   315  // Remove keys for a specified backend type.
   316  func (be *b2Backend) removeKeys(ctx context.Context, t restic.FileType) error {
   317  	debug.Log("removeKeys %v", t)
   318  	for key := range be.List(ctx, t) {
   319  		err := be.Remove(ctx, restic.Handle{Type: t, Name: key})
   320  		if err != nil {
   321  			return err
   322  		}
   323  	}
   324  	return nil
   325  }
   326  
   327  // Delete removes all restic keys in the bucket. It will not remove the bucket itself.
   328  func (be *b2Backend) Delete(ctx context.Context) error {
   329  	alltypes := []restic.FileType{
   330  		restic.DataFile,
   331  		restic.KeyFile,
   332  		restic.LockFile,
   333  		restic.SnapshotFile,
   334  		restic.IndexFile}
   335  
   336  	for _, t := range alltypes {
   337  		err := be.removeKeys(ctx, t)
   338  		if err != nil {
   339  			return nil
   340  		}
   341  	}
   342  	err := be.Remove(ctx, restic.Handle{Type: restic.ConfigFile})
   343  	if err != nil && b2.IsNotExist(errors.Cause(err)) {
   344  		err = nil
   345  	}
   346  
   347  	return err
   348  }
   349  
   350  // Close does nothing
   351  func (be *b2Backend) Close() error { return nil }