github.com/10XDev/rclone@v1.52.3-0.20200626220027-16af9ab76b2a/backend/googlephotos/googlephotos.go (about)

     1  // Package googlephotos provides an interface to Google Photos
     2  package googlephotos
     3  
     4  // FIXME Resumable uploads not implemented - rclone can't resume uploads in general
     5  
     6  import (
     7  	"context"
     8  	"encoding/json"
     9  	"fmt"
    10  	"io"
    11  	golog "log"
    12  	"net/http"
    13  	"net/url"
    14  	"path"
    15  	"regexp"
    16  	"strconv"
    17  	"strings"
    18  	"sync"
    19  	"time"
    20  
    21  	"github.com/pkg/errors"
    22  	"github.com/rclone/rclone/backend/googlephotos/api"
    23  	"github.com/rclone/rclone/fs"
    24  	"github.com/rclone/rclone/fs/config"
    25  	"github.com/rclone/rclone/fs/config/configmap"
    26  	"github.com/rclone/rclone/fs/config/configstruct"
    27  	"github.com/rclone/rclone/fs/config/obscure"
    28  	"github.com/rclone/rclone/fs/dirtree"
    29  	"github.com/rclone/rclone/fs/fserrors"
    30  	"github.com/rclone/rclone/fs/fshttp"
    31  	"github.com/rclone/rclone/fs/hash"
    32  	"github.com/rclone/rclone/fs/log"
    33  	"github.com/rclone/rclone/lib/oauthutil"
    34  	"github.com/rclone/rclone/lib/pacer"
    35  	"github.com/rclone/rclone/lib/rest"
    36  	"golang.org/x/oauth2"
    37  	"golang.org/x/oauth2/google"
    38  )
    39  
    40  var (
    41  	errCantUpload  = errors.New("can't upload files here")
    42  	errCantMkdir   = errors.New("can't make directories here")
    43  	errCantRmdir   = errors.New("can't remove this directory")
    44  	errAlbumDelete = errors.New("google photos API does not implement deleting albums")
    45  	errRemove      = errors.New("google photos API only implements removing files from albums")
    46  	errOwnAlbums   = errors.New("google photos API only allows uploading to albums rclone created")
    47  )
    48  
    49  const (
    50  	rcloneClientID              = "202264815644-rt1o1c9evjaotbpbab10m83i8cnjk077.apps.googleusercontent.com"
    51  	rcloneEncryptedClientSecret = "kLJLretPefBgrDHosdml_nlF64HZ9mUcO85X5rdjYBPP8ChA-jr3Ow"
    52  	rootURL                     = "https://photoslibrary.googleapis.com/v1"
    53  	listChunks                  = 100 // chunk size to read directory listings
    54  	albumChunks                 = 50  // chunk size to read album listings
    55  	minSleep                    = 10 * time.Millisecond
    56  	scopeReadOnly               = "https://www.googleapis.com/auth/photoslibrary.readonly"
    57  	scopeReadWrite              = "https://www.googleapis.com/auth/photoslibrary"
    58  )
    59  
    60  var (
    61  	// Description of how to auth for this app
    62  	oauthConfig = &oauth2.Config{
    63  		Scopes: []string{
    64  			"openid",
    65  			"profile",
    66  			scopeReadWrite,
    67  		},
    68  		Endpoint:     google.Endpoint,
    69  		ClientID:     rcloneClientID,
    70  		ClientSecret: obscure.MustReveal(rcloneEncryptedClientSecret),
    71  		RedirectURL:  oauthutil.TitleBarRedirectURL,
    72  	}
    73  )
    74  
    75  // Register with Fs
    76  func init() {
    77  	fs.Register(&fs.RegInfo{
    78  		Name:        "google photos",
    79  		Prefix:      "gphotos",
    80  		Description: "Google Photos",
    81  		NewFs:       NewFs,
    82  		Config: func(name string, m configmap.Mapper) {
    83  			// Parse config into Options struct
    84  			opt := new(Options)
    85  			err := configstruct.Set(m, opt)
    86  			if err != nil {
    87  				fs.Errorf(nil, "Couldn't parse config into struct: %v", err)
    88  				return
    89  			}
    90  
    91  			// Fill in the scopes
    92  			if opt.ReadOnly {
    93  				oauthConfig.Scopes[0] = scopeReadOnly
    94  			} else {
    95  				oauthConfig.Scopes[0] = scopeReadWrite
    96  			}
    97  
    98  			// Do the oauth
    99  			err = oauthutil.Config("google photos", name, m, oauthConfig, nil)
   100  			if err != nil {
   101  				golog.Fatalf("Failed to configure token: %v", err)
   102  			}
   103  
   104  			// Warn the user
   105  			fmt.Print(`
   106  *** IMPORTANT: All media items uploaded to Google Photos with rclone
   107  *** are stored in full resolution at original quality.  These uploads
   108  *** will count towards storage in your Google Account.
   109  
   110  `)
   111  
   112  		},
   113  		Options: []fs.Option{{
   114  			Name: config.ConfigClientID,
   115  			Help: "Google Application Client Id\nLeave blank normally.",
   116  		}, {
   117  			Name: config.ConfigClientSecret,
   118  			Help: "Google Application Client Secret\nLeave blank normally.",
   119  		}, {
   120  			Name:    "read_only",
   121  			Default: false,
   122  			Help: `Set to make the Google Photos backend read only.
   123  
   124  If you choose read only then rclone will only request read only access
   125  to your photos, otherwise rclone will request full access.`,
   126  		}, {
   127  			Name:    "read_size",
   128  			Default: false,
   129  			Help: `Set to read the size of media items.
   130  
   131  Normally rclone does not read the size of media items since this takes
   132  another transaction.  This isn't necessary for syncing.  However
   133  rclone mount needs to know the size of files in advance of reading
   134  them, so setting this flag when using rclone mount is recommended if
   135  you want to read the media.`,
   136  			Advanced: true,
   137  		}, {
   138  			Name:     "start_year",
   139  			Default:  2000,
   140  			Help:     `Year limits the photos to be downloaded to those which are uploaded after the given year`,
   141  			Advanced: true,
   142  		}},
   143  	})
   144  }
   145  
   146  // Options defines the configuration for this backend
   147  type Options struct {
   148  	ReadOnly  bool `config:"read_only"`
   149  	ReadSize  bool `config:"read_size"`
   150  	StartYear int  `config:"start_year"`
   151  }
   152  
   153  // Fs represents a remote storage server
   154  type Fs struct {
   155  	name       string                 // name of this remote
   156  	root       string                 // the path we are working on if any
   157  	opt        Options                // parsed options
   158  	features   *fs.Features           // optional features
   159  	unAuth     *rest.Client           // unauthenticated http client
   160  	srv        *rest.Client           // the connection to the one drive server
   161  	ts         *oauthutil.TokenSource // token source for oauth2
   162  	pacer      *fs.Pacer              // To pace the API calls
   163  	startTime  time.Time              // time Fs was started - used for datestamps
   164  	albumsMu   sync.Mutex             // protect albums (but not contents)
   165  	albums     map[bool]*albums       // albums, shared or not
   166  	uploadedMu sync.Mutex             // to protect the below
   167  	uploaded   dirtree.DirTree        // record of uploaded items
   168  	createMu   sync.Mutex             // held when creating albums to prevent dupes
   169  }
   170  
   171  // Object describes a storage object
   172  //
   173  // Will definitely have info but maybe not meta
   174  type Object struct {
   175  	fs       *Fs       // what this object is part of
   176  	remote   string    // The remote path
   177  	url      string    // download path
   178  	id       string    // ID of this object
   179  	bytes    int64     // Bytes in the object
   180  	modTime  time.Time // Modified time of the object
   181  	mimeType string
   182  }
   183  
   184  // ------------------------------------------------------------
   185  
   186  // Name of the remote (as passed into NewFs)
   187  func (f *Fs) Name() string {
   188  	return f.name
   189  }
   190  
   191  // Root of the remote (as passed into NewFs)
   192  func (f *Fs) Root() string {
   193  	return f.root
   194  }
   195  
   196  // String converts this Fs to a string
   197  func (f *Fs) String() string {
   198  	return fmt.Sprintf("Google Photos path %q", f.root)
   199  }
   200  
   201  // Features returns the optional features of this Fs
   202  func (f *Fs) Features() *fs.Features {
   203  	return f.features
   204  }
   205  
   206  // dirTime returns the time to set a directory to
   207  func (f *Fs) dirTime() time.Time {
   208  	return f.startTime
   209  }
   210  
   211  // startYear returns the start year
   212  func (f *Fs) startYear() int {
   213  	return f.opt.StartYear
   214  }
   215  
   216  // retryErrorCodes is a slice of error codes that we will retry
   217  var retryErrorCodes = []int{
   218  	429, // Too Many Requests.
   219  	500, // Internal Server Error
   220  	502, // Bad Gateway
   221  	503, // Service Unavailable
   222  	504, // Gateway Timeout
   223  	509, // Bandwidth Limit Exceeded
   224  }
   225  
   226  // shouldRetry returns a boolean as to whether this resp and err
   227  // deserve to be retried.  It returns the err as a convenience
   228  func shouldRetry(resp *http.Response, err error) (bool, error) {
   229  	return fserrors.ShouldRetry(err) || fserrors.ShouldRetryHTTP(resp, retryErrorCodes), err
   230  }
   231  
   232  // errorHandler parses a non 2xx error response into an error
   233  func errorHandler(resp *http.Response) error {
   234  	body, err := rest.ReadBody(resp)
   235  	if err != nil {
   236  		body = nil
   237  	}
   238  	// Google sends 404 messages as images so be prepared for that
   239  	if strings.HasPrefix(resp.Header.Get("Content-Type"), "image/") {
   240  		body = []byte("Image not found or broken")
   241  	}
   242  	var e = api.Error{
   243  		Details: api.ErrorDetails{
   244  			Code:    resp.StatusCode,
   245  			Message: string(body),
   246  			Status:  resp.Status,
   247  		},
   248  	}
   249  	if body != nil {
   250  		_ = json.Unmarshal(body, &e)
   251  	}
   252  	return &e
   253  }
   254  
   255  // NewFs constructs an Fs from the path, bucket:path
   256  func NewFs(name, root string, m configmap.Mapper) (fs.Fs, error) {
   257  	// Parse config into Options struct
   258  	opt := new(Options)
   259  	err := configstruct.Set(m, opt)
   260  	if err != nil {
   261  		return nil, err
   262  	}
   263  
   264  	baseClient := fshttp.NewClient(fs.Config)
   265  	oAuthClient, ts, err := oauthutil.NewClientWithBaseClient(name, m, oauthConfig, baseClient)
   266  	if err != nil {
   267  		return nil, errors.Wrap(err, "failed to configure Box")
   268  	}
   269  
   270  	root = strings.Trim(path.Clean(root), "/")
   271  	if root == "." || root == "/" {
   272  		root = ""
   273  	}
   274  
   275  	f := &Fs{
   276  		name:      name,
   277  		root:      root,
   278  		opt:       *opt,
   279  		unAuth:    rest.NewClient(baseClient),
   280  		srv:       rest.NewClient(oAuthClient).SetRoot(rootURL),
   281  		ts:        ts,
   282  		pacer:     fs.NewPacer(pacer.NewGoogleDrive(pacer.MinSleep(minSleep))),
   283  		startTime: time.Now(),
   284  		albums:    map[bool]*albums{},
   285  		uploaded:  dirtree.New(),
   286  	}
   287  	f.features = (&fs.Features{
   288  		ReadMimeType: true,
   289  	}).Fill(f)
   290  	f.srv.SetErrorHandler(errorHandler)
   291  
   292  	_, _, pattern := patterns.match(f.root, "", true)
   293  	if pattern != nil && pattern.isFile {
   294  		oldRoot := f.root
   295  		var leaf string
   296  		f.root, leaf = path.Split(f.root)
   297  		f.root = strings.TrimRight(f.root, "/")
   298  		_, err := f.NewObject(context.TODO(), leaf)
   299  		if err == nil {
   300  			return f, fs.ErrorIsFile
   301  		}
   302  		f.root = oldRoot
   303  	}
   304  	return f, nil
   305  }
   306  
   307  // fetchEndpoint gets the openid endpoint named from the Google config
   308  func (f *Fs) fetchEndpoint(ctx context.Context, name string) (endpoint string, err error) {
   309  	// Get openID config without auth
   310  	opts := rest.Opts{
   311  		Method:  "GET",
   312  		RootURL: "https://accounts.google.com/.well-known/openid-configuration",
   313  	}
   314  	var openIDconfig map[string]interface{}
   315  	err = f.pacer.Call(func() (bool, error) {
   316  		resp, err := f.unAuth.CallJSON(ctx, &opts, nil, &openIDconfig)
   317  		return shouldRetry(resp, err)
   318  	})
   319  	if err != nil {
   320  		return "", errors.Wrap(err, "couldn't read openID config")
   321  	}
   322  
   323  	// Find userinfo endpoint
   324  	endpoint, ok := openIDconfig[name].(string)
   325  	if !ok {
   326  		return "", errors.Errorf("couldn't find %q from openID config", name)
   327  	}
   328  
   329  	return endpoint, nil
   330  }
   331  
   332  // UserInfo fetches info about the current user with oauth2
   333  func (f *Fs) UserInfo(ctx context.Context) (userInfo map[string]string, err error) {
   334  	endpoint, err := f.fetchEndpoint(ctx, "userinfo_endpoint")
   335  	if err != nil {
   336  		return nil, err
   337  	}
   338  
   339  	// Fetch the user info with auth
   340  	opts := rest.Opts{
   341  		Method:  "GET",
   342  		RootURL: endpoint,
   343  	}
   344  	err = f.pacer.Call(func() (bool, error) {
   345  		resp, err := f.srv.CallJSON(ctx, &opts, nil, &userInfo)
   346  		return shouldRetry(resp, err)
   347  	})
   348  	if err != nil {
   349  		return nil, errors.Wrap(err, "couldn't read user info")
   350  	}
   351  	return userInfo, nil
   352  }
   353  
   354  // Disconnect kills the token and refresh token
   355  func (f *Fs) Disconnect(ctx context.Context) (err error) {
   356  	endpoint, err := f.fetchEndpoint(ctx, "revocation_endpoint")
   357  	if err != nil {
   358  		return err
   359  	}
   360  	token, err := f.ts.Token()
   361  	if err != nil {
   362  		return err
   363  	}
   364  
   365  	// Revoke the token and the refresh token
   366  	opts := rest.Opts{
   367  		Method:  "POST",
   368  		RootURL: endpoint,
   369  		MultipartParams: url.Values{
   370  			"token":           []string{token.AccessToken},
   371  			"token_type_hint": []string{"access_token"},
   372  		},
   373  	}
   374  	var res interface{}
   375  	err = f.pacer.Call(func() (bool, error) {
   376  		resp, err := f.srv.CallJSON(ctx, &opts, nil, &res)
   377  		return shouldRetry(resp, err)
   378  	})
   379  	if err != nil {
   380  		return errors.Wrap(err, "couldn't revoke token")
   381  	}
   382  	fs.Infof(f, "res = %+v", res)
   383  	return nil
   384  }
   385  
   386  // Return an Object from a path
   387  //
   388  // If it can't be found it returns the error fs.ErrorObjectNotFound.
   389  func (f *Fs) newObjectWithInfo(ctx context.Context, remote string, info *api.MediaItem) (fs.Object, error) {
   390  	o := &Object{
   391  		fs:     f,
   392  		remote: remote,
   393  	}
   394  	if info != nil {
   395  		o.setMetaData(info)
   396  	} else {
   397  		err := o.readMetaData(ctx) // reads info and meta, returning an error
   398  		if err != nil {
   399  			return nil, err
   400  		}
   401  	}
   402  	return o, nil
   403  }
   404  
   405  // NewObject finds the Object at remote.  If it can't be found
   406  // it returns the error fs.ErrorObjectNotFound.
   407  func (f *Fs) NewObject(ctx context.Context, remote string) (fs.Object, error) {
   408  	defer log.Trace(f, "remote=%q", remote)("")
   409  	return f.newObjectWithInfo(ctx, remote, nil)
   410  }
   411  
   412  // addID adds the ID to name
   413  func addID(name string, ID string) string {
   414  	idStr := "{" + ID + "}"
   415  	if name == "" {
   416  		return idStr
   417  	}
   418  	return name + " " + idStr
   419  }
   420  
   421  // addFileID adds the ID to the fileName passed in
   422  func addFileID(fileName string, ID string) string {
   423  	ext := path.Ext(fileName)
   424  	base := fileName[:len(fileName)-len(ext)]
   425  	return addID(base, ID) + ext
   426  }
   427  
   428  var idRe = regexp.MustCompile(`\{([A-Za-z0-9_-]{55,})\}`)
   429  
   430  // findID finds an ID in string if one is there or ""
   431  func findID(name string) string {
   432  	match := idRe.FindStringSubmatch(name)
   433  	if match == nil {
   434  		return ""
   435  	}
   436  	return match[1]
   437  }
   438  
   439  // list the albums into an internal cache
   440  // FIXME cache invalidation
   441  func (f *Fs) listAlbums(ctx context.Context, shared bool) (all *albums, err error) {
   442  	f.albumsMu.Lock()
   443  	defer f.albumsMu.Unlock()
   444  	all, ok := f.albums[shared]
   445  	if ok && all != nil {
   446  		return all, nil
   447  	}
   448  	opts := rest.Opts{
   449  		Method:     "GET",
   450  		Path:       "/albums",
   451  		Parameters: url.Values{},
   452  	}
   453  	if shared {
   454  		opts.Path = "/sharedAlbums"
   455  	}
   456  	all = newAlbums()
   457  	opts.Parameters.Set("pageSize", strconv.Itoa(albumChunks))
   458  	lastID := ""
   459  	for {
   460  		var result api.ListAlbums
   461  		var resp *http.Response
   462  		err = f.pacer.Call(func() (bool, error) {
   463  			resp, err = f.srv.CallJSON(ctx, &opts, nil, &result)
   464  			return shouldRetry(resp, err)
   465  		})
   466  		if err != nil {
   467  			return nil, errors.Wrap(err, "couldn't list albums")
   468  		}
   469  		newAlbums := result.Albums
   470  		if shared {
   471  			newAlbums = result.SharedAlbums
   472  		}
   473  		if len(newAlbums) > 0 && newAlbums[0].ID == lastID {
   474  			// skip first if ID duplicated from last page
   475  			newAlbums = newAlbums[1:]
   476  		}
   477  		if len(newAlbums) > 0 {
   478  			lastID = newAlbums[len(newAlbums)-1].ID
   479  		}
   480  		for i := range newAlbums {
   481  			all.add(&newAlbums[i])
   482  		}
   483  		if result.NextPageToken == "" {
   484  			break
   485  		}
   486  		opts.Parameters.Set("pageToken", result.NextPageToken)
   487  	}
   488  	f.albums[shared] = all
   489  	return all, nil
   490  }
   491  
   492  // listFn is called from list to handle an object.
   493  type listFn func(remote string, object *api.MediaItem, isDirectory bool) error
   494  
   495  // list the objects into the function supplied
   496  //
   497  // dir is the starting directory, "" for root
   498  //
   499  // Set recurse to read sub directories
   500  func (f *Fs) list(ctx context.Context, filter api.SearchFilter, fn listFn) (err error) {
   501  	opts := rest.Opts{
   502  		Method: "POST",
   503  		Path:   "/mediaItems:search",
   504  	}
   505  	filter.PageSize = listChunks
   506  	filter.PageToken = ""
   507  	lastID := ""
   508  	for {
   509  		var result api.MediaItems
   510  		var resp *http.Response
   511  		err = f.pacer.Call(func() (bool, error) {
   512  			resp, err = f.srv.CallJSON(ctx, &opts, &filter, &result)
   513  			return shouldRetry(resp, err)
   514  		})
   515  		if err != nil {
   516  			return errors.Wrap(err, "couldn't list files")
   517  		}
   518  		items := result.MediaItems
   519  		if len(items) > 0 && items[0].ID == lastID {
   520  			// skip first if ID duplicated from last page
   521  			items = items[1:]
   522  		}
   523  		if len(items) > 0 {
   524  			lastID = items[len(items)-1].ID
   525  		}
   526  		for i := range items {
   527  			item := &result.MediaItems[i]
   528  			remote := item.Filename
   529  			remote = strings.Replace(remote, "/", "/", -1)
   530  			err = fn(remote, item, false)
   531  			if err != nil {
   532  				return err
   533  			}
   534  		}
   535  		if result.NextPageToken == "" {
   536  			break
   537  		}
   538  		filter.PageToken = result.NextPageToken
   539  	}
   540  
   541  	return nil
   542  }
   543  
   544  // Convert a list item into a DirEntry
   545  func (f *Fs) itemToDirEntry(ctx context.Context, remote string, item *api.MediaItem, isDirectory bool) (fs.DirEntry, error) {
   546  	if isDirectory {
   547  		d := fs.NewDir(remote, f.dirTime())
   548  		return d, nil
   549  	}
   550  	o := &Object{
   551  		fs:     f,
   552  		remote: remote,
   553  	}
   554  	o.setMetaData(item)
   555  	return o, nil
   556  }
   557  
   558  // listDir lists a single directory
   559  func (f *Fs) listDir(ctx context.Context, prefix string, filter api.SearchFilter) (entries fs.DirEntries, err error) {
   560  	// List the objects
   561  	err = f.list(ctx, filter, func(remote string, item *api.MediaItem, isDirectory bool) error {
   562  		entry, err := f.itemToDirEntry(ctx, prefix+remote, item, isDirectory)
   563  		if err != nil {
   564  			return err
   565  		}
   566  		if entry != nil {
   567  			entries = append(entries, entry)
   568  		}
   569  		return nil
   570  	})
   571  	if err != nil {
   572  		return nil, err
   573  	}
   574  	// Dedupe the file names
   575  	dupes := map[string]int{}
   576  	for _, entry := range entries {
   577  		o, ok := entry.(*Object)
   578  		if ok {
   579  			dupes[o.remote]++
   580  		}
   581  	}
   582  	for _, entry := range entries {
   583  		o, ok := entry.(*Object)
   584  		if ok {
   585  			duplicated := dupes[o.remote] > 1
   586  			if duplicated || o.remote == "" {
   587  				o.remote = addFileID(o.remote, o.id)
   588  			}
   589  		}
   590  	}
   591  	return entries, err
   592  }
   593  
   594  // listUploads lists a single directory from the uploads
   595  func (f *Fs) listUploads(ctx context.Context, dir string) (entries fs.DirEntries, err error) {
   596  	f.uploadedMu.Lock()
   597  	entries, ok := f.uploaded[dir]
   598  	f.uploadedMu.Unlock()
   599  	if !ok && dir != "" {
   600  		return nil, fs.ErrorDirNotFound
   601  	}
   602  	return entries, nil
   603  }
   604  
   605  // List the objects and directories in dir into entries.  The
   606  // entries can be returned in any order but should be for a
   607  // complete directory.
   608  //
   609  // dir should be "" to list the root, and should not have
   610  // trailing slashes.
   611  //
   612  // This should return ErrDirNotFound if the directory isn't
   613  // found.
   614  func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err error) {
   615  	defer log.Trace(f, "dir=%q", dir)("err=%v", &err)
   616  	match, prefix, pattern := patterns.match(f.root, dir, false)
   617  	if pattern == nil || pattern.isFile {
   618  		return nil, fs.ErrorDirNotFound
   619  	}
   620  	if pattern.toEntries != nil {
   621  		return pattern.toEntries(ctx, f, prefix, match)
   622  	}
   623  	return nil, fs.ErrorDirNotFound
   624  }
   625  
   626  // Put the object into the bucket
   627  //
   628  // Copy the reader in to the new object which is returned
   629  //
   630  // The new object may have been created if an error is returned
   631  func (f *Fs) Put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
   632  	defer log.Trace(f, "src=%+v", src)("")
   633  	// Temporary Object under construction
   634  	o := &Object{
   635  		fs:     f,
   636  		remote: src.Remote(),
   637  	}
   638  	return o, o.Update(ctx, in, src, options...)
   639  }
   640  
   641  // createAlbum creates the album
   642  func (f *Fs) createAlbum(ctx context.Context, albumTitle string) (album *api.Album, err error) {
   643  	opts := rest.Opts{
   644  		Method:     "POST",
   645  		Path:       "/albums",
   646  		Parameters: url.Values{},
   647  	}
   648  	var request = api.CreateAlbum{
   649  		Album: &api.Album{
   650  			Title: albumTitle,
   651  		},
   652  	}
   653  	var result api.Album
   654  	var resp *http.Response
   655  	err = f.pacer.Call(func() (bool, error) {
   656  		resp, err = f.srv.CallJSON(ctx, &opts, request, &result)
   657  		return shouldRetry(resp, err)
   658  	})
   659  	if err != nil {
   660  		return nil, errors.Wrap(err, "couldn't create album")
   661  	}
   662  	f.albums[false].add(&result)
   663  	return &result, nil
   664  }
   665  
   666  // getOrCreateAlbum gets an existing album or creates a new one
   667  //
   668  // It does the creation with the lock held to avoid duplicates
   669  func (f *Fs) getOrCreateAlbum(ctx context.Context, albumTitle string) (album *api.Album, err error) {
   670  	f.createMu.Lock()
   671  	defer f.createMu.Unlock()
   672  	albums, err := f.listAlbums(ctx, false)
   673  	if err != nil {
   674  		return nil, err
   675  	}
   676  	album, ok := albums.get(albumTitle)
   677  	if ok {
   678  		return album, nil
   679  	}
   680  	return f.createAlbum(ctx, albumTitle)
   681  }
   682  
   683  // Mkdir creates the album if it doesn't exist
   684  func (f *Fs) Mkdir(ctx context.Context, dir string) (err error) {
   685  	defer log.Trace(f, "dir=%q", dir)("err=%v", &err)
   686  	match, prefix, pattern := patterns.match(f.root, dir, false)
   687  	if pattern == nil {
   688  		return fs.ErrorDirNotFound
   689  	}
   690  	if !pattern.canMkdir {
   691  		return errCantMkdir
   692  	}
   693  	if pattern.isUpload {
   694  		f.uploadedMu.Lock()
   695  		d := fs.NewDir(strings.Trim(prefix, "/"), f.dirTime())
   696  		f.uploaded.AddEntry(d)
   697  		f.uploadedMu.Unlock()
   698  		return nil
   699  	}
   700  	albumTitle := match[1]
   701  	_, err = f.getOrCreateAlbum(ctx, albumTitle)
   702  	return err
   703  }
   704  
   705  // Rmdir deletes the bucket if the fs is at the root
   706  //
   707  // Returns an error if it isn't empty
   708  func (f *Fs) Rmdir(ctx context.Context, dir string) (err error) {
   709  	defer log.Trace(f, "dir=%q")("err=%v", &err)
   710  	match, _, pattern := patterns.match(f.root, dir, false)
   711  	if pattern == nil {
   712  		return fs.ErrorDirNotFound
   713  	}
   714  	if !pattern.canMkdir {
   715  		return errCantRmdir
   716  	}
   717  	if pattern.isUpload {
   718  		f.uploadedMu.Lock()
   719  		err = f.uploaded.Prune(map[string]bool{
   720  			dir: true,
   721  		})
   722  		f.uploadedMu.Unlock()
   723  		return err
   724  	}
   725  	albumTitle := match[1]
   726  	allAlbums, err := f.listAlbums(ctx, false)
   727  	if err != nil {
   728  		return err
   729  	}
   730  	album, ok := allAlbums.get(albumTitle)
   731  	if !ok {
   732  		return fs.ErrorDirNotFound
   733  	}
   734  	_ = album
   735  	return errAlbumDelete
   736  }
   737  
   738  // Precision returns the precision
   739  func (f *Fs) Precision() time.Duration {
   740  	return fs.ModTimeNotSupported
   741  }
   742  
   743  // Hashes returns the supported hash sets.
   744  func (f *Fs) Hashes() hash.Set {
   745  	return hash.Set(hash.None)
   746  }
   747  
   748  // ------------------------------------------------------------
   749  
   750  // Fs returns the parent Fs
   751  func (o *Object) Fs() fs.Info {
   752  	return o.fs
   753  }
   754  
   755  // Return a string version
   756  func (o *Object) String() string {
   757  	if o == nil {
   758  		return "<nil>"
   759  	}
   760  	return o.remote
   761  }
   762  
   763  // Remote returns the remote path
   764  func (o *Object) Remote() string {
   765  	return o.remote
   766  }
   767  
   768  // Hash returns the Md5sum of an object returning a lowercase hex string
   769  func (o *Object) Hash(ctx context.Context, t hash.Type) (string, error) {
   770  	return "", hash.ErrUnsupported
   771  }
   772  
   773  // Size returns the size of an object in bytes
   774  func (o *Object) Size() int64 {
   775  	defer log.Trace(o, "")("")
   776  	if !o.fs.opt.ReadSize || o.bytes >= 0 {
   777  		return o.bytes
   778  	}
   779  	ctx := context.TODO()
   780  	err := o.readMetaData(ctx)
   781  	if err != nil {
   782  		fs.Debugf(o, "Size: Failed to read metadata: %v", err)
   783  		return -1
   784  	}
   785  	var resp *http.Response
   786  	opts := rest.Opts{
   787  		Method:  "HEAD",
   788  		RootURL: o.downloadURL(),
   789  	}
   790  	err = o.fs.pacer.Call(func() (bool, error) {
   791  		resp, err = o.fs.srv.Call(ctx, &opts)
   792  		return shouldRetry(resp, err)
   793  	})
   794  	if err != nil {
   795  		fs.Debugf(o, "Reading size failed: %v", err)
   796  	} else {
   797  		lengthStr := resp.Header.Get("Content-Length")
   798  		length, err := strconv.ParseInt(lengthStr, 10, 64)
   799  		if err != nil {
   800  			fs.Debugf(o, "Reading size failed to parse Content_length %q: %v", lengthStr, err)
   801  		} else {
   802  			o.bytes = length
   803  		}
   804  	}
   805  	return o.bytes
   806  }
   807  
   808  // setMetaData sets the fs data from a storage.Object
   809  func (o *Object) setMetaData(info *api.MediaItem) {
   810  	o.url = info.BaseURL
   811  	o.id = info.ID
   812  	o.bytes = -1 // FIXME
   813  	o.mimeType = info.MimeType
   814  	o.modTime = info.MediaMetadata.CreationTime
   815  }
   816  
   817  // readMetaData gets the metadata if it hasn't already been fetched
   818  //
   819  // it also sets the info
   820  func (o *Object) readMetaData(ctx context.Context) (err error) {
   821  	if !o.modTime.IsZero() && o.url != "" {
   822  		return nil
   823  	}
   824  	dir, fileName := path.Split(o.remote)
   825  	dir = strings.Trim(dir, "/")
   826  	_, _, pattern := patterns.match(o.fs.root, o.remote, true)
   827  	if pattern == nil {
   828  		return fs.ErrorObjectNotFound
   829  	}
   830  	if !pattern.isFile {
   831  		return fs.ErrorNotAFile
   832  	}
   833  	// If have ID fetch it directly
   834  	if id := findID(fileName); id != "" {
   835  		opts := rest.Opts{
   836  			Method: "GET",
   837  			Path:   "/mediaItems/" + id,
   838  		}
   839  		var item api.MediaItem
   840  		var resp *http.Response
   841  		err = o.fs.pacer.Call(func() (bool, error) {
   842  			resp, err = o.fs.srv.CallJSON(ctx, &opts, nil, &item)
   843  			return shouldRetry(resp, err)
   844  		})
   845  		if err != nil {
   846  			return errors.Wrap(err, "couldn't get media item")
   847  		}
   848  		o.setMetaData(&item)
   849  		return nil
   850  	}
   851  	// Otherwise list the directory the file is in
   852  	entries, err := o.fs.List(ctx, dir)
   853  	if err != nil {
   854  		if err == fs.ErrorDirNotFound {
   855  			return fs.ErrorObjectNotFound
   856  		}
   857  		return err
   858  	}
   859  	// and find the file in the directory
   860  	for _, entry := range entries {
   861  		if entry.Remote() == o.remote {
   862  			if newO, ok := entry.(*Object); ok {
   863  				*o = *newO
   864  				return nil
   865  			}
   866  		}
   867  	}
   868  	return fs.ErrorObjectNotFound
   869  }
   870  
   871  // ModTime returns the modification time of the object
   872  //
   873  // It attempts to read the objects mtime and if that isn't present the
   874  // LastModified returned in the http headers
   875  func (o *Object) ModTime(ctx context.Context) time.Time {
   876  	defer log.Trace(o, "")("")
   877  	err := o.readMetaData(ctx)
   878  	if err != nil {
   879  		fs.Debugf(o, "ModTime: Failed to read metadata: %v", err)
   880  		return time.Now()
   881  	}
   882  	return o.modTime
   883  }
   884  
   885  // SetModTime sets the modification time of the local fs object
   886  func (o *Object) SetModTime(ctx context.Context, modTime time.Time) (err error) {
   887  	return fs.ErrorCantSetModTime
   888  }
   889  
   890  // Storable returns a boolean as to whether this object is storable
   891  func (o *Object) Storable() bool {
   892  	return true
   893  }
   894  
   895  // downloadURL returns the URL for a full bytes download for the object
   896  func (o *Object) downloadURL() string {
   897  	url := o.url + "=d"
   898  	if strings.HasPrefix(o.mimeType, "video/") {
   899  		url += "v"
   900  	}
   901  	return url
   902  }
   903  
   904  // Open an object for read
   905  func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (in io.ReadCloser, err error) {
   906  	defer log.Trace(o, "")("")
   907  	err = o.readMetaData(ctx)
   908  	if err != nil {
   909  		fs.Debugf(o, "Open: Failed to read metadata: %v", err)
   910  		return nil, err
   911  	}
   912  	var resp *http.Response
   913  	opts := rest.Opts{
   914  		Method:  "GET",
   915  		RootURL: o.downloadURL(),
   916  		Options: options,
   917  	}
   918  	err = o.fs.pacer.Call(func() (bool, error) {
   919  		resp, err = o.fs.srv.Call(ctx, &opts)
   920  		return shouldRetry(resp, err)
   921  	})
   922  	if err != nil {
   923  		return nil, err
   924  	}
   925  	return resp.Body, err
   926  }
   927  
   928  // Update the object with the contents of the io.Reader, modTime and size
   929  //
   930  // The new object may have been created if an error is returned
   931  func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (err error) {
   932  	defer log.Trace(o, "src=%+v", src)("err=%v", &err)
   933  	match, _, pattern := patterns.match(o.fs.root, o.remote, true)
   934  	if pattern == nil || !pattern.isFile || !pattern.canUpload {
   935  		return errCantUpload
   936  	}
   937  	var (
   938  		albumID  string
   939  		fileName string
   940  	)
   941  	if pattern.isUpload {
   942  		fileName = match[1]
   943  	} else {
   944  		var albumTitle string
   945  		albumTitle, fileName = match[1], match[2]
   946  
   947  		album, err := o.fs.getOrCreateAlbum(ctx, albumTitle)
   948  		if err != nil {
   949  			return err
   950  		}
   951  
   952  		if !album.IsWriteable {
   953  			return errOwnAlbums
   954  		}
   955  
   956  		albumID = album.ID
   957  	}
   958  
   959  	// Upload the media item in exchange for an UploadToken
   960  	opts := rest.Opts{
   961  		Method:  "POST",
   962  		Path:    "/uploads",
   963  		Options: options,
   964  		ExtraHeaders: map[string]string{
   965  			"X-Goog-Upload-File-Name": fileName,
   966  			"X-Goog-Upload-Protocol":  "raw",
   967  		},
   968  		Body: in,
   969  	}
   970  	var token []byte
   971  	var resp *http.Response
   972  	err = o.fs.pacer.CallNoRetry(func() (bool, error) {
   973  		resp, err = o.fs.srv.Call(ctx, &opts)
   974  		if err != nil {
   975  			return shouldRetry(resp, err)
   976  		}
   977  		token, err = rest.ReadBody(resp)
   978  		return shouldRetry(resp, err)
   979  	})
   980  	if err != nil {
   981  		return errors.Wrap(err, "couldn't upload file")
   982  	}
   983  	uploadToken := strings.TrimSpace(string(token))
   984  	if uploadToken == "" {
   985  		return errors.New("empty upload token")
   986  	}
   987  
   988  	// Create the media item from an UploadToken, optionally adding to an album
   989  	opts = rest.Opts{
   990  		Method: "POST",
   991  		Path:   "/mediaItems:batchCreate",
   992  	}
   993  	var request = api.BatchCreateRequest{
   994  		AlbumID: albumID,
   995  		NewMediaItems: []api.NewMediaItem{
   996  			{
   997  				SimpleMediaItem: api.SimpleMediaItem{
   998  					UploadToken: uploadToken,
   999  				},
  1000  			},
  1001  		},
  1002  	}
  1003  	var result api.BatchCreateResponse
  1004  	err = o.fs.pacer.Call(func() (bool, error) {
  1005  		resp, err = o.fs.srv.CallJSON(ctx, &opts, request, &result)
  1006  		return shouldRetry(resp, err)
  1007  	})
  1008  	if err != nil {
  1009  		return errors.Wrap(err, "failed to create media item")
  1010  	}
  1011  	if len(result.NewMediaItemResults) != 1 {
  1012  		return errors.New("bad response to BatchCreate wrong number of items")
  1013  	}
  1014  	mediaItemResult := result.NewMediaItemResults[0]
  1015  	if mediaItemResult.Status.Code != 0 {
  1016  		return errors.Errorf("upload failed: %s (%d)", mediaItemResult.Status.Message, mediaItemResult.Status.Code)
  1017  	}
  1018  	o.setMetaData(&mediaItemResult.MediaItem)
  1019  
  1020  	// Add upload to internal storage
  1021  	if pattern.isUpload {
  1022  		o.fs.uploadedMu.Lock()
  1023  		o.fs.uploaded.AddEntry(o)
  1024  		o.fs.uploadedMu.Unlock()
  1025  	}
  1026  	return nil
  1027  }
  1028  
  1029  // Remove an object
  1030  func (o *Object) Remove(ctx context.Context) (err error) {
  1031  	match, _, pattern := patterns.match(o.fs.root, o.remote, true)
  1032  	if pattern == nil || !pattern.isFile || !pattern.canUpload || pattern.isUpload {
  1033  		return errRemove
  1034  	}
  1035  	albumTitle, fileName := match[1], match[2]
  1036  	album, ok := o.fs.albums[false].get(albumTitle)
  1037  	if !ok {
  1038  		return errors.Errorf("couldn't file %q in album %q for delete", fileName, albumTitle)
  1039  	}
  1040  	opts := rest.Opts{
  1041  		Method:     "POST",
  1042  		Path:       "/albums/" + album.ID + ":batchRemoveMediaItems",
  1043  		NoResponse: true,
  1044  	}
  1045  	var request = api.BatchRemoveItems{
  1046  		MediaItemIds: []string{o.id},
  1047  	}
  1048  	var resp *http.Response
  1049  	err = o.fs.pacer.Call(func() (bool, error) {
  1050  		resp, err = o.fs.srv.CallJSON(ctx, &opts, &request, nil)
  1051  		return shouldRetry(resp, err)
  1052  	})
  1053  	if err != nil {
  1054  		return errors.Wrap(err, "couldn't delete item from album")
  1055  	}
  1056  	return nil
  1057  }
  1058  
  1059  // MimeType of an Object if known, "" otherwise
  1060  func (o *Object) MimeType(ctx context.Context) string {
  1061  	return o.mimeType
  1062  }
  1063  
  1064  // ID of an Object if known, "" otherwise
  1065  func (o *Object) ID() string {
  1066  	return o.id
  1067  }
  1068  
  1069  // Check the interfaces are satisfied
  1070  var (
  1071  	_ fs.Fs           = &Fs{}
  1072  	_ fs.UserInfoer   = &Fs{}
  1073  	_ fs.Disconnecter = &Fs{}
  1074  	_ fs.Object       = &Object{}
  1075  	_ fs.MimeTyper    = &Object{}
  1076  	_ fs.IDer         = &Object{}
  1077  )