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 }