github.com/artpar/rclone@v1.67.3/backend/drive/metadata.go (about)

     1  package drive
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"strconv"
     8  	"strings"
     9  	"sync"
    10  
    11  	"github.com/artpar/rclone/fs"
    12  	"github.com/artpar/rclone/lib/errcount"
    13  	"golang.org/x/sync/errgroup"
    14  	drive "google.golang.org/api/drive/v3"
    15  	"google.golang.org/api/googleapi"
    16  )
    17  
    18  // system metadata keys which this backend owns
    19  var systemMetadataInfo = map[string]fs.MetadataHelp{
    20  	"content-type": {
    21  		Help:    "The MIME type of the file.",
    22  		Type:    "string",
    23  		Example: "text/plain",
    24  	},
    25  	"mtime": {
    26  		Help:    "Time of last modification with mS accuracy.",
    27  		Type:    "RFC 3339",
    28  		Example: "2006-01-02T15:04:05.999Z07:00",
    29  	},
    30  	"btime": {
    31  		Help:    "Time of file birth (creation) with mS accuracy. Note that this is only writable on fresh uploads - it can't be written for updates.",
    32  		Type:    "RFC 3339",
    33  		Example: "2006-01-02T15:04:05.999Z07:00",
    34  	},
    35  	"copy-requires-writer-permission": {
    36  		Help:    "Whether the options to copy, print, or download this file, should be disabled for readers and commenters.",
    37  		Type:    "boolean",
    38  		Example: "true",
    39  	},
    40  	"writers-can-share": {
    41  		Help:    "Whether users with only writer permission can modify the file's permissions. Not populated and ignored when setting for items in shared drives.",
    42  		Type:    "boolean",
    43  		Example: "false",
    44  	},
    45  	"viewed-by-me": {
    46  		Help:     "Whether the file has been viewed by this user.",
    47  		Type:     "boolean",
    48  		Example:  "true",
    49  		ReadOnly: true,
    50  	},
    51  	"owner": {
    52  		Help:    "The owner of the file. Usually an email address. Enable with --drive-metadata-owner.",
    53  		Type:    "string",
    54  		Example: "user@example.com",
    55  	},
    56  	"permissions": {
    57  		Help:    "Permissions in a JSON dump of Google drive format. On shared drives these will only be present if they aren't inherited. Enable with --drive-metadata-permissions.",
    58  		Type:    "JSON",
    59  		Example: "{}",
    60  	},
    61  	"folder-color-rgb": {
    62  		Help:    "The color for a folder or a shortcut to a folder as an RGB hex string.",
    63  		Type:    "string",
    64  		Example: "881133",
    65  	},
    66  	"description": {
    67  		Help:    "A short description of the file.",
    68  		Type:    "string",
    69  		Example: "Contract for signing",
    70  	},
    71  	"starred": {
    72  		Help:    "Whether the user has starred the file.",
    73  		Type:    "boolean",
    74  		Example: "false",
    75  	},
    76  	"labels": {
    77  		Help:    "Labels attached to this file in a JSON dump of Googled drive format. Enable with --drive-metadata-labels.",
    78  		Type:    "JSON",
    79  		Example: "[]",
    80  	},
    81  }
    82  
    83  // Extra fields we need to fetch to implement the system metadata above
    84  var metadataFields = googleapi.Field(strings.Join([]string{
    85  	"copyRequiresWriterPermission",
    86  	"description",
    87  	"folderColorRgb",
    88  	"hasAugmentedPermissions",
    89  	"owners",
    90  	"permissionIds",
    91  	"permissions",
    92  	"properties",
    93  	"starred",
    94  	"viewedByMe",
    95  	"viewedByMeTime",
    96  	"writersCanShare",
    97  }, ","))
    98  
    99  // Fields we need to read from permissions
   100  var permissionsFields = googleapi.Field(strings.Join([]string{
   101  	"*",
   102  	"permissionDetails/*",
   103  }, ","))
   104  
   105  // getPermission returns permissions for the fileID and permissionID passed in
   106  func (f *Fs) getPermission(ctx context.Context, fileID, permissionID string, useCache bool) (perm *drive.Permission, inherited bool, err error) {
   107  	f.permissionsMu.Lock()
   108  	defer f.permissionsMu.Unlock()
   109  	if useCache {
   110  		perm = f.permissions[permissionID]
   111  		if perm != nil {
   112  			return perm, false, nil
   113  		}
   114  	}
   115  	fs.Debugf(f, "Fetching permission %q", permissionID)
   116  	err = f.pacer.Call(func() (bool, error) {
   117  		perm, err = f.svc.Permissions.Get(fileID, permissionID).
   118  			Fields(permissionsFields).
   119  			SupportsAllDrives(true).
   120  			Context(ctx).Do()
   121  		return f.shouldRetry(ctx, err)
   122  	})
   123  	if err != nil {
   124  		return nil, false, err
   125  	}
   126  
   127  	inherited = len(perm.PermissionDetails) > 0 && perm.PermissionDetails[0].Inherited
   128  
   129  	cleanPermission(perm)
   130  
   131  	// cache the permission
   132  	f.permissions[permissionID] = perm
   133  
   134  	return perm, inherited, err
   135  }
   136  
   137  // Set the permissions on the info
   138  func (f *Fs) setPermissions(ctx context.Context, info *drive.File, permissions []*drive.Permission) (err error) {
   139  	errs := errcount.New()
   140  	for _, perm := range permissions {
   141  		if perm.Role == "owner" {
   142  			// ignore owner permissions - these are set with owner
   143  			continue
   144  		}
   145  		cleanPermissionForWrite(perm)
   146  		err := f.pacer.Call(func() (bool, error) {
   147  			_, err := f.svc.Permissions.Create(info.Id, perm).
   148  				SupportsAllDrives(true).
   149  				SendNotificationEmail(false).
   150  				Context(ctx).Do()
   151  			return f.shouldRetry(ctx, err)
   152  		})
   153  		if err != nil {
   154  			fs.Errorf(f, "Failed to set permission: %v", err)
   155  			errs.Add(err)
   156  		}
   157  	}
   158  	return errs.Err("failed to set permission")
   159  }
   160  
   161  // Clean attributes from permissions which we can't write
   162  func cleanPermissionForWrite(perm *drive.Permission) {
   163  	perm.Deleted = false
   164  	perm.DisplayName = ""
   165  	perm.Id = ""
   166  	perm.Kind = ""
   167  	perm.PermissionDetails = nil
   168  	perm.TeamDrivePermissionDetails = nil
   169  }
   170  
   171  // Clean and cache the permission if not already cached
   172  func (f *Fs) cleanAndCachePermission(perm *drive.Permission) {
   173  	f.permissionsMu.Lock()
   174  	defer f.permissionsMu.Unlock()
   175  	cleanPermission(perm)
   176  	if _, found := f.permissions[perm.Id]; !found {
   177  		f.permissions[perm.Id] = perm
   178  	}
   179  }
   180  
   181  // Clean fields we don't need to keep from the permission
   182  func cleanPermission(perm *drive.Permission) {
   183  	// DisplayName: Output only. The "pretty" name of the value of the
   184  	// permission. The following is a list of examples for each type of
   185  	// permission: * `user` - User's full name, as defined for their Google
   186  	// account, such as "Joe Smith." * `group` - Name of the Google Group,
   187  	// such as "The Company Administrators." * `domain` - String domain
   188  	// name, such as "thecompany.com." * `anyone` - No `displayName` is
   189  	// present.
   190  	perm.DisplayName = ""
   191  
   192  	// Kind: Output only. Identifies what kind of resource this is. Value:
   193  	// the fixed string "drive#permission".
   194  	perm.Kind = ""
   195  
   196  	// PermissionDetails: Output only. Details of whether the permissions on
   197  	// this shared drive item are inherited or directly on this item. This
   198  	// is an output-only field which is present only for shared drive items.
   199  	perm.PermissionDetails = nil
   200  
   201  	// PhotoLink: Output only. A link to the user's profile photo, if
   202  	// available.
   203  	perm.PhotoLink = ""
   204  
   205  	// TeamDrivePermissionDetails: Output only. Deprecated: Output only. Use
   206  	// `permissionDetails` instead.
   207  	perm.TeamDrivePermissionDetails = nil
   208  }
   209  
   210  // Fields we need to read from labels
   211  var labelsFields = googleapi.Field(strings.Join([]string{
   212  	"*",
   213  }, ","))
   214  
   215  // getLabels returns labels for the fileID passed in
   216  func (f *Fs) getLabels(ctx context.Context, fileID string) (labels []*drive.Label, err error) {
   217  	fs.Debugf(f, "Fetching labels for %q", fileID)
   218  	listLabels := f.svc.Files.ListLabels(fileID).
   219  		Fields(labelsFields).
   220  		Context(ctx)
   221  	for {
   222  		var info *drive.LabelList
   223  		err = f.pacer.Call(func() (bool, error) {
   224  			info, err = listLabels.Do()
   225  			return f.shouldRetry(ctx, err)
   226  		})
   227  		if err != nil {
   228  			return nil, err
   229  		}
   230  		labels = append(labels, info.Labels...)
   231  		if info.NextPageToken == "" {
   232  			break
   233  		}
   234  		listLabels.PageToken(info.NextPageToken)
   235  	}
   236  	for _, label := range labels {
   237  		cleanLabel(label)
   238  	}
   239  	return labels, nil
   240  }
   241  
   242  // Set the labels on the info
   243  func (f *Fs) setLabels(ctx context.Context, info *drive.File, labels []*drive.Label) (err error) {
   244  	if len(labels) == 0 {
   245  		return nil
   246  	}
   247  	req := drive.ModifyLabelsRequest{}
   248  	for _, label := range labels {
   249  		req.LabelModifications = append(req.LabelModifications, &drive.LabelModification{
   250  			FieldModifications: labelFieldsToFieldModifications(label.Fields),
   251  			LabelId:            label.Id,
   252  		})
   253  	}
   254  	err = f.pacer.Call(func() (bool, error) {
   255  		_, err = f.svc.Files.ModifyLabels(info.Id, &req).
   256  			Context(ctx).Do()
   257  		return f.shouldRetry(ctx, err)
   258  	})
   259  	if err != nil {
   260  		return fmt.Errorf("failed to set owner: %w", err)
   261  	}
   262  	return nil
   263  }
   264  
   265  // Convert label fields into something which can set the fields
   266  func labelFieldsToFieldModifications(fields map[string]drive.LabelField) (out []*drive.LabelFieldModification) {
   267  	for id, field := range fields {
   268  		var emails []string
   269  		for _, user := range field.User {
   270  			emails = append(emails, user.EmailAddress)
   271  		}
   272  		out = append(out, &drive.LabelFieldModification{
   273  			// FieldId: The ID of the field to be modified.
   274  			FieldId: id,
   275  
   276  			// SetDateValues: Replaces the value of a dateString Field with these
   277  			// new values. The string must be in the RFC 3339 full-date format:
   278  			// YYYY-MM-DD.
   279  			SetDateValues: field.DateString,
   280  
   281  			// SetIntegerValues: Replaces the value of an `integer` field with these
   282  			// new values.
   283  			SetIntegerValues: field.Integer,
   284  
   285  			// SetSelectionValues: Replaces a `selection` field with these new
   286  			// values.
   287  			SetSelectionValues: field.Selection,
   288  
   289  			// SetTextValues: Sets the value of a `text` field.
   290  			SetTextValues: field.Text,
   291  
   292  			// SetUserValues: Replaces a `user` field with these new values. The
   293  			// values must be valid email addresses.
   294  			SetUserValues: emails,
   295  		})
   296  	}
   297  	return out
   298  }
   299  
   300  // Clean fields we don't need to keep from the label
   301  func cleanLabel(label *drive.Label) {
   302  	// Kind: This is always drive#label
   303  	label.Kind = ""
   304  
   305  	for name, field := range label.Fields {
   306  		// Kind: This is always drive#labelField.
   307  		field.Kind = ""
   308  
   309  		// Note the fields are copies so we need to write them
   310  		// back to the map
   311  		label.Fields[name] = field
   312  	}
   313  }
   314  
   315  // Parse the metadata from drive item
   316  //
   317  // It should return nil if there is no Metadata
   318  func (o *baseObject) parseMetadata(ctx context.Context, info *drive.File) (err error) {
   319  	metadata := make(fs.Metadata, 16)
   320  
   321  	// Dump user metadata first as it overrides system metadata
   322  	for k, v := range info.Properties {
   323  		metadata[k] = v
   324  	}
   325  
   326  	// System metadata
   327  	metadata["copy-requires-writer-permission"] = fmt.Sprint(info.CopyRequiresWriterPermission)
   328  	metadata["writers-can-share"] = fmt.Sprint(info.WritersCanShare)
   329  	metadata["viewed-by-me"] = fmt.Sprint(info.ViewedByMe)
   330  	metadata["content-type"] = info.MimeType
   331  
   332  	// Owners: Output only. The owner of this file. Only certain legacy
   333  	// files may have more than one owner. This field isn't populated for
   334  	// items in shared drives.
   335  	if o.fs.opt.MetadataOwner.IsSet(rwRead) && len(info.Owners) > 0 {
   336  		user := info.Owners[0]
   337  		if len(info.Owners) > 1 {
   338  			fs.Logf(o, "Ignoring more than 1 owner")
   339  		}
   340  		if user != nil {
   341  			id := user.EmailAddress
   342  			if id == "" {
   343  				id = user.DisplayName
   344  			}
   345  			metadata["owner"] = id
   346  		}
   347  	}
   348  
   349  	if o.fs.opt.MetadataPermissions.IsSet(rwRead) {
   350  		// We only write permissions out if they are not inherited.
   351  		//
   352  		// On My Drives permissions seem to be attached to every item
   353  		// so they will always be written out.
   354  		//
   355  		// On Shared Drives only non-inherited permissions will be
   356  		// written out.
   357  
   358  		// To read the inherited permissions flag will mean we need to
   359  		// read the permissions for each object and the cache will be
   360  		// useless. However shared drives don't return permissions
   361  		// only permissionIds so will need to fetch them for each
   362  		// object. We use HasAugmentedPermissions to see if there are
   363  		// special permissions before fetching them to save transactions.
   364  
   365  		// HasAugmentedPermissions: Output only. Whether there are permissions
   366  		// directly on this file. This field is only populated for items in
   367  		// shared drives.
   368  		if o.fs.isTeamDrive && !info.HasAugmentedPermissions {
   369  			// Don't process permissions if there aren't any specifically set
   370  			info.Permissions = nil
   371  			info.PermissionIds = nil
   372  		}
   373  
   374  		// PermissionIds: Output only. List of permission IDs for users with
   375  		// access to this file.
   376  		//
   377  		// Only process these if we have no Permissions
   378  		if len(info.PermissionIds) > 0 && len(info.Permissions) == 0 {
   379  			info.Permissions = make([]*drive.Permission, 0, len(info.PermissionIds))
   380  			g, gCtx := errgroup.WithContext(ctx)
   381  			g.SetLimit(o.fs.ci.Checkers)
   382  			var mu sync.Mutex // protect the info.Permissions from concurrent writes
   383  			for _, permissionID := range info.PermissionIds {
   384  				permissionID := permissionID
   385  				g.Go(func() error {
   386  					// must fetch the team drive ones individually to check the inherited flag
   387  					perm, inherited, err := o.fs.getPermission(gCtx, actualID(info.Id), permissionID, !o.fs.isTeamDrive)
   388  					if err != nil {
   389  						return fmt.Errorf("failed to read permission: %w", err)
   390  					}
   391  					// Don't write inherited permissions out
   392  					if inherited {
   393  						return nil
   394  					}
   395  					// Don't write owner role out - these are covered by the owner metadata
   396  					if perm.Role == "owner" {
   397  						return nil
   398  					}
   399  					mu.Lock()
   400  					info.Permissions = append(info.Permissions, perm)
   401  					mu.Unlock()
   402  					return nil
   403  				})
   404  			}
   405  			err = g.Wait()
   406  			if err != nil {
   407  				return err
   408  			}
   409  		} else {
   410  			// Clean the fetched permissions
   411  			for _, perm := range info.Permissions {
   412  				o.fs.cleanAndCachePermission(perm)
   413  			}
   414  		}
   415  
   416  		// Permissions: Output only. The full list of permissions for the file.
   417  		// This is only available if the requesting user can share the file. Not
   418  		// populated for items in shared drives.
   419  		if len(info.Permissions) > 0 {
   420  			buf, err := json.Marshal(info.Permissions)
   421  			if err != nil {
   422  				return fmt.Errorf("failed to marshal permissions: %w", err)
   423  			}
   424  			metadata["permissions"] = string(buf)
   425  		}
   426  
   427  		// Permission propagation
   428  		// https://developers.google.com/drive/api/guides/manage-sharing#permission-propagation
   429  		// Leads me to believe that in non shared drives, permissions
   430  		// are added to each item when you set permissions for a
   431  		// folder whereas in shared drives they are inherited and
   432  		// placed on the item directly.
   433  	}
   434  
   435  	if info.FolderColorRgb != "" {
   436  		metadata["folder-color-rgb"] = info.FolderColorRgb
   437  	}
   438  	if info.Description != "" {
   439  		metadata["description"] = info.Description
   440  	}
   441  	metadata["starred"] = fmt.Sprint(info.Starred)
   442  	metadata["btime"] = info.CreatedTime
   443  	metadata["mtime"] = info.ModifiedTime
   444  
   445  	if o.fs.opt.MetadataLabels.IsSet(rwRead) {
   446  		// FIXME would be really nice if we knew if files had labels
   447  		// before listing but we need to know all possible label IDs
   448  		// to get it in the listing.
   449  
   450  		labels, err := o.fs.getLabels(ctx, actualID(info.Id))
   451  		if err != nil {
   452  			return fmt.Errorf("failed to fetch labels: %w", err)
   453  		}
   454  		buf, err := json.Marshal(labels)
   455  		if err != nil {
   456  			return fmt.Errorf("failed to marshal labels: %w", err)
   457  		}
   458  		metadata["labels"] = string(buf)
   459  	}
   460  
   461  	o.metadata = &metadata
   462  	return nil
   463  }
   464  
   465  // Set the owner on the info
   466  func (f *Fs) setOwner(ctx context.Context, info *drive.File, owner string) (err error) {
   467  	perm := drive.Permission{
   468  		Role:         "owner",
   469  		EmailAddress: owner,
   470  		// Type: The type of the grantee. Valid values are: * `user` * `group` *
   471  		// `domain` * `anyone` When creating a permission, if `type` is `user`
   472  		// or `group`, you must provide an `emailAddress` for the user or group.
   473  		// When `type` is `domain`, you must provide a `domain`. There isn't
   474  		// extra information required for an `anyone` type.
   475  		Type: "user",
   476  	}
   477  	err = f.pacer.Call(func() (bool, error) {
   478  		_, err = f.svc.Permissions.Create(info.Id, &perm).
   479  			SupportsAllDrives(true).
   480  			TransferOwnership(true).
   481  			// SendNotificationEmail(false). - required apparently!
   482  			Context(ctx).Do()
   483  		return f.shouldRetry(ctx, err)
   484  	})
   485  	if err != nil {
   486  		return fmt.Errorf("failed to set owner: %w", err)
   487  	}
   488  	return nil
   489  }
   490  
   491  // Call back to set metadata that can't be set on the upload/update
   492  //
   493  // The *drive.File passed in holds the current state of the drive.File
   494  // and this should update it with any modifications.
   495  type updateMetadataFn func(context.Context, *drive.File) error
   496  
   497  // read the metadata from meta and write it into updateInfo
   498  //
   499  // update should be true if this is being used to create metadata for
   500  // an update/PATCH call as the rules on what can be updated are
   501  // slightly different there.
   502  //
   503  // It returns a callback which should be called to finish the updates
   504  // after the data is uploaded.
   505  func (f *Fs) updateMetadata(ctx context.Context, updateInfo *drive.File, meta fs.Metadata, update bool) (callback updateMetadataFn, err error) {
   506  	callbackFns := []updateMetadataFn{}
   507  	callback = func(ctx context.Context, info *drive.File) error {
   508  		for _, fn := range callbackFns {
   509  			err := fn(ctx, info)
   510  			if err != nil {
   511  				return err
   512  			}
   513  		}
   514  		return nil
   515  	}
   516  	// merge metadata into request and user metadata
   517  	for k, v := range meta {
   518  		k, v := k, v
   519  		// parse a boolean from v and write into out
   520  		parseBool := func(out *bool) error {
   521  			b, err := strconv.ParseBool(v)
   522  			if err != nil {
   523  				return fmt.Errorf("can't parse metadata %q = %q: %w", k, v, err)
   524  			}
   525  			*out = b
   526  			return nil
   527  		}
   528  		switch k {
   529  		case "copy-requires-writer-permission":
   530  			if err := parseBool(&updateInfo.CopyRequiresWriterPermission); err != nil {
   531  				return nil, err
   532  			}
   533  		case "writers-can-share":
   534  			if !f.isTeamDrive {
   535  				if err := parseBool(&updateInfo.WritersCanShare); err != nil {
   536  					return nil, err
   537  				}
   538  			} else {
   539  				fs.Debugf(f, "Ignoring %s=%s as can't set on shared drives", k, v)
   540  			}
   541  		case "viewed-by-me":
   542  			// Can't write this
   543  		case "content-type":
   544  			updateInfo.MimeType = v
   545  		case "owner":
   546  			if !f.opt.MetadataOwner.IsSet(rwWrite) {
   547  				continue
   548  			}
   549  			// Can't set Owner on upload so need to set afterwards
   550  			callbackFns = append(callbackFns, func(ctx context.Context, info *drive.File) error {
   551  				return f.setOwner(ctx, info, v)
   552  			})
   553  		case "permissions":
   554  			if !f.opt.MetadataPermissions.IsSet(rwWrite) {
   555  				continue
   556  			}
   557  			var perms []*drive.Permission
   558  			err := json.Unmarshal([]byte(v), &perms)
   559  			if err != nil {
   560  				return nil, fmt.Errorf("failed to unmarshal permissions: %w", err)
   561  			}
   562  			// Can't set Permissions on upload so need to set afterwards
   563  			callbackFns = append(callbackFns, func(ctx context.Context, info *drive.File) error {
   564  				return f.setPermissions(ctx, info, perms)
   565  			})
   566  		case "labels":
   567  			if !f.opt.MetadataLabels.IsSet(rwWrite) {
   568  				continue
   569  			}
   570  			var labels []*drive.Label
   571  			err := json.Unmarshal([]byte(v), &labels)
   572  			if err != nil {
   573  				return nil, fmt.Errorf("failed to unmarshal labels: %w", err)
   574  			}
   575  			// Can't set Labels on upload so need to set afterwards
   576  			callbackFns = append(callbackFns, func(ctx context.Context, info *drive.File) error {
   577  				return f.setLabels(ctx, info, labels)
   578  			})
   579  		case "folder-color-rgb":
   580  			updateInfo.FolderColorRgb = v
   581  		case "description":
   582  			updateInfo.Description = v
   583  		case "starred":
   584  			if err := parseBool(&updateInfo.Starred); err != nil {
   585  				return nil, err
   586  			}
   587  		case "btime":
   588  			if update {
   589  				fs.Debugf(f, "Skipping btime metadata as can't update it on an existing file: %v", v)
   590  			} else {
   591  				updateInfo.CreatedTime = v
   592  			}
   593  		case "mtime":
   594  			updateInfo.ModifiedTime = v
   595  		default:
   596  			if updateInfo.Properties == nil {
   597  				updateInfo.Properties = make(map[string]string, 1)
   598  			}
   599  			updateInfo.Properties[k] = v
   600  		}
   601  	}
   602  	return callback, nil
   603  }
   604  
   605  // Fetch metadata and update updateInfo if --metadata is in use
   606  func (f *Fs) fetchAndUpdateMetadata(ctx context.Context, src fs.ObjectInfo, options []fs.OpenOption, updateInfo *drive.File, update bool) (callback updateMetadataFn, err error) {
   607  	meta, err := fs.GetMetadataOptions(ctx, f, src, options)
   608  	if err != nil {
   609  		return nil, fmt.Errorf("failed to read metadata from source object: %w", err)
   610  	}
   611  	callback, err = f.updateMetadata(ctx, updateInfo, meta, update)
   612  	if err != nil {
   613  		return nil, fmt.Errorf("failed to update metadata from source object: %w", err)
   614  	}
   615  	return callback, nil
   616  }