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