github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/backend/onedrive/metadata.go (about) 1 package onedrive 2 3 import ( 4 "context" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "net/http" 9 "strings" 10 "time" 11 12 "github.com/rclone/rclone/backend/onedrive/api" 13 "github.com/rclone/rclone/fs" 14 "github.com/rclone/rclone/fs/fserrors" 15 "github.com/rclone/rclone/lib/dircache" 16 "github.com/rclone/rclone/lib/errcount" 17 "golang.org/x/exp/slices" // replace with slices after go1.21 is the minimum version 18 ) 19 20 const ( 21 dirMimeType = "inode/directory" 22 timeFormatIn = time.RFC3339 23 timeFormatOut = "2006-01-02T15:04:05.999Z" // mS for OneDrive Personal, otherwise only S 24 ) 25 26 // system metadata keys which this backend owns 27 var systemMetadataInfo = map[string]fs.MetadataHelp{ 28 "content-type": { 29 Help: "The MIME type of the file.", 30 Type: "string", 31 Example: "text/plain", 32 ReadOnly: true, 33 }, 34 "mtime": { 35 Help: "Time of last modification with S accuracy (mS for OneDrive Personal).", 36 Type: "RFC 3339", 37 Example: "2006-01-02T15:04:05Z", 38 }, 39 "btime": { 40 Help: "Time of file birth (creation) with S accuracy (mS for OneDrive Personal).", 41 Type: "RFC 3339", 42 Example: "2006-01-02T15:04:05Z", 43 }, 44 "utime": { 45 Help: "Time of upload with S accuracy (mS for OneDrive Personal).", 46 Type: "RFC 3339", 47 Example: "2006-01-02T15:04:05Z", 48 ReadOnly: true, 49 }, 50 "created-by-display-name": { 51 Help: "Display name of the user that created the item.", 52 Type: "string", 53 Example: "John Doe", 54 ReadOnly: true, 55 }, 56 "created-by-id": { 57 Help: "ID of the user that created the item.", 58 Type: "string", 59 Example: "48d31887-5fad-4d73-a9f5-3c356e68a038", 60 ReadOnly: true, 61 }, 62 "description": { 63 Help: "A short description of the file. Max 1024 characters. Only supported for OneDrive Personal.", 64 Type: "string", 65 Example: "Contract for signing", 66 }, 67 "id": { 68 Help: "The unique identifier of the item within OneDrive.", 69 Type: "string", 70 Example: "01BYE5RZ6QN3ZWBTUFOFD3GSPGOHDJD36K", 71 ReadOnly: true, 72 }, 73 "last-modified-by-display-name": { 74 Help: "Display name of the user that last modified the item.", 75 Type: "string", 76 Example: "John Doe", 77 ReadOnly: true, 78 }, 79 "last-modified-by-id": { 80 Help: "ID of the user that last modified the item.", 81 Type: "string", 82 Example: "48d31887-5fad-4d73-a9f5-3c356e68a038", 83 ReadOnly: true, 84 }, 85 "malware-detected": { 86 Help: "Whether OneDrive has detected that the item contains malware.", 87 Type: "boolean", 88 Example: "true", 89 ReadOnly: true, 90 }, 91 "package-type": { 92 Help: "If present, indicates that this item is a package instead of a folder or file. Packages are treated like files in some contexts and folders in others.", 93 Type: "string", 94 Example: "oneNote", 95 ReadOnly: true, 96 }, 97 "shared-owner-id": { 98 Help: "ID of the owner of the shared item (if shared).", 99 Type: "string", 100 Example: "48d31887-5fad-4d73-a9f5-3c356e68a038", 101 ReadOnly: true, 102 }, 103 "shared-by-id": { 104 Help: "ID of the user that shared the item (if shared).", 105 Type: "string", 106 Example: "48d31887-5fad-4d73-a9f5-3c356e68a038", 107 ReadOnly: true, 108 }, 109 "shared-scope": { 110 Help: "If shared, indicates the scope of how the item is shared: anonymous, organization, or users.", 111 Type: "string", 112 Example: "users", 113 ReadOnly: true, 114 }, 115 "shared-time": { 116 Help: "Time when the item was shared, with S accuracy (mS for OneDrive Personal).", 117 Type: "RFC 3339", 118 Example: "2006-01-02T15:04:05Z", 119 ReadOnly: true, 120 }, 121 "permissions": { 122 Help: "Permissions in a JSON dump of OneDrive format. Enable with --onedrive-metadata-permissions. Properties: id, grantedTo, grantedToIdentities, invitation, inheritedFrom, link, roles, shareId", 123 Type: "JSON", 124 Example: "{}", 125 }, 126 } 127 128 // rwChoices type for fs.Bits 129 type rwChoices struct{} 130 131 func (rwChoices) Choices() []fs.BitsChoicesInfo { 132 return []fs.BitsChoicesInfo{ 133 {Bit: uint64(rwOff), Name: "off"}, 134 {Bit: uint64(rwRead), Name: "read"}, 135 {Bit: uint64(rwWrite), Name: "write"}, 136 {Bit: uint64(rwFailOK), Name: "failok"}, 137 } 138 } 139 140 // rwChoice type alias 141 type rwChoice = fs.Bits[rwChoices] 142 143 const ( 144 rwRead rwChoice = 1 << iota 145 rwWrite 146 rwFailOK 147 rwOff rwChoice = 0 148 ) 149 150 // Examples for the options 151 var rwExamples = fs.OptionExamples{{ 152 Value: rwOff.String(), 153 Help: "Do not read or write the value", 154 }, { 155 Value: rwRead.String(), 156 Help: "Read the value only", 157 }, { 158 Value: rwWrite.String(), 159 Help: "Write the value only", 160 }, { 161 Value: (rwRead | rwWrite).String(), 162 Help: "Read and Write the value.", 163 }, { 164 Value: rwFailOK.String(), 165 Help: "If writing fails log errors only, don't fail the transfer", 166 }} 167 168 // Metadata describes metadata properties shared by both Objects and Directories 169 type Metadata struct { 170 fs *Fs // what this object/dir is part of 171 remote string // remote, for convenience when obj/dir not in scope 172 mimeType string // Content-Type of object from server (may not be as uploaded) 173 description string // Provides a user-visible description of the item. Read-write. Only on OneDrive Personal 174 mtime time.Time // Time of last modification with S accuracy. 175 btime time.Time // Time of file birth (creation) with S accuracy. 176 utime time.Time // Time of upload with S accuracy. 177 createdBy api.IdentitySet // user that created the item 178 lastModifiedBy api.IdentitySet // user that last modified the item 179 malwareDetected bool // Whether OneDrive has detected that the item contains malware. 180 packageType string // If present, indicates that this item is a package instead of a folder or file. 181 shared *api.SharedType // information about the shared state of the item, if shared 182 normalizedID string // the normalized ID of the object or dir 183 permissions []*api.PermissionsType // The current set of permissions for the item. Note that to save API calls, this is not guaranteed to be cached on the object. Use m.Get() to refresh. 184 queuedPermissions []*api.PermissionsType // The set of permissions queued to be updated. 185 permsAddOnly bool // Whether to disable "update" and "remove" (for example, during server-side copy when the dst will have new IDs) 186 } 187 188 // Get retrieves the cached metadata and converts it to fs.Metadata. 189 // This is most typically used when OneDrive is the source (as opposed to the dest). 190 // If m.fs.opt.MetadataPermissions includes "read" then this will also include permissions, which requires an API call. 191 // Get does not use an API call otherwise. 192 func (m *Metadata) Get(ctx context.Context) (metadata fs.Metadata, err error) { 193 metadata = make(fs.Metadata, 17) 194 metadata["content-type"] = m.mimeType 195 metadata["mtime"] = m.mtime.Format(timeFormatOut) 196 metadata["btime"] = m.btime.Format(timeFormatOut) 197 metadata["utime"] = m.utime.Format(timeFormatOut) 198 metadata["created-by-display-name"] = m.createdBy.User.DisplayName 199 metadata["created-by-id"] = m.createdBy.User.ID 200 if m.description != "" { 201 metadata["description"] = m.description 202 } 203 metadata["id"] = m.normalizedID 204 metadata["last-modified-by-display-name"] = m.lastModifiedBy.User.DisplayName 205 metadata["last-modified-by-id"] = m.lastModifiedBy.User.ID 206 metadata["malware-detected"] = fmt.Sprint(m.malwareDetected) 207 if m.packageType != "" { 208 metadata["package-type"] = m.packageType 209 } 210 if m.shared != nil { 211 metadata["shared-owner-id"] = m.shared.Owner.User.ID 212 metadata["shared-by-id"] = m.shared.SharedBy.User.ID 213 metadata["shared-scope"] = m.shared.Scope 214 metadata["shared-time"] = time.Time(m.shared.SharedDateTime).Format(timeFormatOut) 215 } 216 if m.fs.opt.MetadataPermissions.IsSet(rwRead) { 217 p, _, err := m.fs.getPermissions(ctx, m.normalizedID) 218 if err != nil { 219 return nil, fmt.Errorf("failed to get permissions: %w", err) 220 } 221 m.permissions = p 222 223 if len(p) > 0 { 224 fs.PrettyPrint(m.permissions, "perms", fs.LogLevelDebug) 225 buf, err := json.Marshal(m.permissions) 226 if err != nil { 227 return nil, fmt.Errorf("failed to marshal permissions: %w", err) 228 } 229 metadata["permissions"] = string(buf) 230 } 231 } 232 return metadata, nil 233 } 234 235 // Set takes fs.Metadata and parses/converts it to cached Metadata. 236 // This is most typically used when OneDrive is the destination (as opposed to the source). 237 // It does not actually update the remote (use Write for that.) 238 // It sets only the writeable metadata properties (i.e. read-only properties are skipped.) 239 // Permissions are included if m.fs.opt.MetadataPermissions includes "write". 240 // It returns errors if writeable properties can't be parsed. 241 // It does not return errors for unsupported properties that may be passed in. 242 // It returns the number of writeable properties set (if it is 0, we can skip the Write API call.) 243 func (m *Metadata) Set(ctx context.Context, metadata fs.Metadata) (numSet int, err error) { 244 numSet = 0 245 for k, v := range metadata { 246 k, v := k, v 247 switch k { 248 case "mtime": 249 t, err := time.Parse(timeFormatIn, v) 250 if err != nil { 251 return numSet, fmt.Errorf("failed to parse metadata %q = %q: %w", k, v, err) 252 } 253 m.mtime = t 254 numSet++ 255 case "btime": 256 t, err := time.Parse(timeFormatIn, v) 257 if err != nil { 258 return numSet, fmt.Errorf("failed to parse metadata %q = %q: %w", k, v, err) 259 } 260 m.btime = t 261 numSet++ 262 case "description": 263 if m.fs.driveType != driveTypePersonal { 264 fs.Debugf(m.remote, "metadata description is only supported for OneDrive Personal -- skipping: %s", v) 265 continue 266 } 267 m.description = v 268 numSet++ 269 case "permissions": 270 if !m.fs.opt.MetadataPermissions.IsSet(rwWrite) { 271 continue 272 } 273 var perms []*api.PermissionsType 274 err := json.Unmarshal([]byte(v), &perms) 275 if err != nil { 276 return numSet, fmt.Errorf("failed to unmarshal permissions: %w", err) 277 } 278 m.queuedPermissions = perms 279 numSet++ 280 default: 281 fs.Debugf(m.remote, "skipping unsupported metadata item: %s: %s", k, v) 282 } 283 } 284 if numSet == 0 { 285 fs.Infof(m.remote, "no writeable metadata found: %v", metadata) 286 } 287 return numSet, nil 288 } 289 290 // toAPIMetadata converts object/dir Metadata to api.Metadata for API calls. 291 // If btime is missing but mtime is present, mtime is also used as the btime, as otherwise it would get overwritten. 292 func (m *Metadata) toAPIMetadata() api.Metadata { 293 update := api.Metadata{ 294 FileSystemInfo: &api.FileSystemInfoFacet{}, 295 } 296 if m.description != "" && m.fs.driveType == driveTypePersonal { 297 update.Description = m.description 298 } 299 if !m.mtime.IsZero() { 300 update.FileSystemInfo.LastModifiedDateTime = api.Timestamp(m.mtime) 301 } 302 if !m.btime.IsZero() { 303 update.FileSystemInfo.CreatedDateTime = api.Timestamp(m.btime) 304 } 305 306 if m.btime.IsZero() && !m.mtime.IsZero() { // use mtime as btime if missing 307 m.btime = m.mtime 308 update.FileSystemInfo.CreatedDateTime = api.Timestamp(m.btime) 309 } 310 return update 311 } 312 313 // Write takes the cached Metadata and sets it on the remote, using API calls. 314 // If m.fs.opt.MetadataPermissions includes "write" and updatePermissions == true, permissions are also set. 315 // Calling Write without any writeable metadata will result in an error. 316 func (m *Metadata) Write(ctx context.Context, updatePermissions bool) (*api.Item, error) { 317 update := m.toAPIMetadata() 318 if update.IsEmpty() { 319 return nil, fmt.Errorf("%v: no writeable metadata found: %v", m.remote, m) 320 } 321 opts := m.fs.newOptsCallWithPath(ctx, m.remote, "PATCH", "") 322 var info *api.Item 323 err := m.fs.pacer.Call(func() (bool, error) { 324 resp, err := m.fs.srv.CallJSON(ctx, &opts, &update, &info) 325 return shouldRetry(ctx, resp, err) 326 }) 327 if err != nil { 328 fs.Debugf(m.remote, "errored metadata: %v", m) 329 return nil, fmt.Errorf("%v: error updating metadata: %v", m.remote, err) 330 } 331 332 if m.fs.opt.MetadataPermissions.IsSet(rwWrite) && updatePermissions { 333 m.normalizedID = info.GetID() 334 err = m.WritePermissions(ctx) 335 if err != nil { 336 fs.Errorf(m.remote, "error writing permissions: %v", err) 337 return info, err 338 } 339 } 340 341 // update the struct since we have fresh info 342 m.fs.setSystemMetadata(info, m, m.remote, m.mimeType) 343 344 return info, err 345 } 346 347 // RefreshPermissions fetches the current permissions from the remote and caches them as Metadata 348 func (m *Metadata) RefreshPermissions(ctx context.Context) (err error) { 349 if m.normalizedID == "" { 350 return errors.New("internal error: normalizedID is missing") 351 } 352 p, _, err := m.fs.getPermissions(ctx, m.normalizedID) 353 if err != nil { 354 return fmt.Errorf("failed to refresh permissions: %w", err) 355 } 356 m.permissions = p 357 return nil 358 } 359 360 // WritePermissions sets the permissions (and no other metadata) on the remote. 361 // m.permissions (the existing perms) and m.queuedPermissions (the new perms to be set) must be set correctly before calling this. 362 // m.permissions == nil will not error, as it is valid to add permissions when there were previously none. 363 // If successful, m.permissions will be set with the new current permissions and m.queuedPermissions will be nil. 364 func (m *Metadata) WritePermissions(ctx context.Context) (err error) { 365 if !m.fs.opt.MetadataPermissions.IsSet(rwWrite) { 366 return errors.New("can't write permissions without --onedrive-metadata-permissions write") 367 } 368 if m.normalizedID == "" { 369 return errors.New("internal error: normalizedID is missing") 370 } 371 if m.fs.opt.MetadataPermissions.IsSet(rwFailOK) { 372 // If failok is set, allow the permissions setting to fail and only log an ERROR 373 defer func() { 374 if err != nil { 375 fs.Errorf(m.fs, "Ignoring error as failok is set: %v", err) 376 err = nil 377 } 378 }() 379 } 380 381 // compare current to queued and sort into add/update/remove queues 382 add, update, remove := m.sortPermissions() 383 fs.Debugf(m.remote, "metadata permissions: to add: %d to update: %d to remove: %d", len(add), len(update), len(remove)) 384 _, err = m.processPermissions(ctx, add, update, remove) 385 if err != nil { 386 return fmt.Errorf("failed to process permissions: %w", err) 387 } 388 389 err = m.RefreshPermissions(ctx) 390 fs.Debugf(m.remote, "updated permissions (now has %d permissions)", len(m.permissions)) 391 if err != nil { 392 return fmt.Errorf("failed to get permissions: %w", err) 393 } 394 m.queuedPermissions = nil 395 396 return nil 397 } 398 399 // sortPermissions sorts the permissions (to be written) into add, update, and remove queues 400 func (m *Metadata) sortPermissions() (add, update, remove []*api.PermissionsType) { 401 new, old := m.queuedPermissions, m.permissions 402 if len(old) == 0 || m.permsAddOnly { 403 return new, nil, nil // they must all be "add" 404 } 405 406 for _, n := range new { 407 if n == nil { 408 continue 409 } 410 if n.ID != "" { 411 // sanity check: ensure there's a matching "old" id with a non-matching role 412 if !slices.ContainsFunc(old, func(o *api.PermissionsType) bool { 413 return o.ID == n.ID && slices.Compare(o.Roles, n.Roles) != 0 && len(o.Roles) > 0 && len(n.Roles) > 0 && !slices.Contains(o.Roles, api.OwnerRole) 414 }) { 415 fs.Debugf(m.remote, "skipping update for invalid roles: %v (perm ID: %v)", n.Roles, n.ID) 416 continue 417 } 418 if m.fs.driveType != driveTypePersonal && n.Link != nil && n.Link.WebURL != "" { 419 // special case to work around API limitation -- can't update a sharing link perm so need to remove + add instead 420 // https://learn.microsoft.com/en-us/answers/questions/986279/why-is-update-permission-graph-api-for-files-not-w 421 // https://github.com/microsoftgraph/msgraph-sdk-dotnet/issues/1135 422 fs.Debugf(m.remote, "sortPermissions: can't update due to API limitation, will remove + add instead: %v", n.Roles) 423 remove = append(remove, n) 424 add = append(add, n) 425 continue 426 } 427 fs.Debugf(m.remote, "sortPermissions: will update role to %v", n.Roles) 428 update = append(update, n) 429 } else { 430 fs.Debugf(m.remote, "sortPermissions: will add permission: %v %v", n, n.Roles) 431 add = append(add, n) 432 } 433 } 434 for _, o := range old { 435 if slices.Contains(o.Roles, api.OwnerRole) { 436 fs.Debugf(m.remote, "skipping remove permission -- can't remove 'owner' role") 437 continue 438 } 439 newHasOld := slices.ContainsFunc(new, func(n *api.PermissionsType) bool { 440 if n == nil || n.ID == "" { 441 return false // can't remove perms without an ID 442 } 443 return n.ID == o.ID 444 }) 445 if !newHasOld && o.ID != "" && !slices.Contains(add, o) && !slices.Contains(update, o) { 446 fs.Debugf(m.remote, "sortPermissions: will remove permission: %v %v (perm ID: %v)", o, o.Roles, o.ID) 447 remove = append(remove, o) 448 } 449 } 450 return add, update, remove 451 } 452 453 // processPermissions executes the add, update, and remove queues for writing permissions 454 func (m *Metadata) processPermissions(ctx context.Context, add, update, remove []*api.PermissionsType) (newPermissions []*api.PermissionsType, err error) { 455 errs := errcount.New() 456 for _, p := range remove { // remove (need to do these first because of remove + add workaround) 457 _, err := m.removePermission(ctx, p) 458 if err != nil { 459 fs.Errorf(m.remote, "Failed to remove permission: %v", err) 460 errs.Add(err) 461 } 462 } 463 464 for _, p := range add { // add 465 newPs, _, err := m.addPermission(ctx, p) 466 if err != nil { 467 fs.Errorf(m.remote, "Failed to add permission: %v", err) 468 errs.Add(err) 469 continue 470 } 471 newPermissions = append(newPermissions, newPs...) 472 } 473 474 for _, p := range update { // update 475 newP, _, err := m.updatePermission(ctx, p) 476 if err != nil { 477 fs.Errorf(m.remote, "Failed to update permission: %v", err) 478 errs.Add(err) 479 continue 480 } 481 newPermissions = append(newPermissions, newP) 482 } 483 484 err = errs.Err("failed to set permissions") 485 if err != nil { 486 err = fserrors.NoRetryError(err) 487 } 488 return newPermissions, err 489 } 490 491 // fillRecipients looks for recipients to add from the permission passed in. 492 // It looks for an email address in identity.User.Email, ID, and DisplayName, otherwise it uses the identity.User.ID as r.ObjectID. 493 // It considers both "GrantedTo" and "GrantedToIdentities". 494 func fillRecipients(p *api.PermissionsType, driveType string) (recipients []api.DriveRecipient) { 495 if p == nil { 496 return recipients 497 } 498 ids := make(map[string]struct{}, len(p.GetGrantedToIdentities(driveType))+1) 499 isUnique := func(s string) bool { 500 _, ok := ids[s] 501 return !ok && s != "" 502 } 503 504 addRecipient := func(identity *api.IdentitySet) { 505 r := api.DriveRecipient{} 506 507 id := "" 508 if strings.ContainsRune(identity.User.Email, '@') { 509 id = identity.User.Email 510 r.Email = id 511 } else if strings.ContainsRune(identity.User.ID, '@') { 512 id = identity.User.ID 513 r.Email = id 514 } else if strings.ContainsRune(identity.User.DisplayName, '@') { 515 id = identity.User.DisplayName 516 r.Email = id 517 } else { 518 id = identity.User.ID 519 r.ObjectID = id 520 } 521 if !isUnique(id) { 522 return 523 } 524 ids[id] = struct{}{} 525 recipients = append(recipients, r) 526 } 527 528 forIdentitySet := func(iSet *api.IdentitySet) { 529 if iSet == nil { 530 return 531 } 532 iS := *iSet 533 forIdentity := func(i api.Identity) { 534 if i != (api.Identity{}) { 535 iS.User = i 536 addRecipient(&iS) 537 } 538 } 539 forIdentity(iS.User) 540 forIdentity(iS.SiteUser) 541 forIdentity(iS.Group) 542 forIdentity(iS.SiteGroup) 543 forIdentity(iS.Application) 544 forIdentity(iS.Device) 545 } 546 547 for _, identitySet := range p.GetGrantedToIdentities(driveType) { 548 forIdentitySet(identitySet) 549 } 550 forIdentitySet(p.GetGrantedTo(driveType)) 551 552 return recipients 553 } 554 555 // addPermission adds new permissions to an object or dir. 556 // if p.Link.Scope == "anonymous" then it will also create a Public Link. 557 func (m *Metadata) addPermission(ctx context.Context, p *api.PermissionsType) (newPs []*api.PermissionsType, resp *http.Response, err error) { 558 opts := m.fs.newOptsCall(m.normalizedID, "POST", "/invite") 559 560 req := &api.AddPermissionsRequest{ 561 Recipients: fillRecipients(p, m.fs.driveType), 562 RequireSignIn: m.fs.driveType != driveTypePersonal, // personal and business have conflicting requirements 563 Roles: p.Roles, 564 } 565 if m.fs.driveType != driveTypePersonal { 566 req.RetainInheritedPermissions = false // not supported for personal 567 } 568 569 if p.Link != nil && p.Link.Scope == api.AnonymousScope { 570 link, err := m.fs.PublicLink(ctx, m.remote, fs.DurationOff, false) 571 if err != nil { 572 return nil, nil, err 573 } 574 p.Link.WebURL = link 575 newPs = append(newPs, p) 576 if len(req.Recipients) == 0 { 577 return newPs, nil, nil 578 } 579 } 580 581 if len(req.Recipients) == 0 { 582 fs.Debugf(m.remote, "skipping add permission -- at least one valid recipient is required") 583 return nil, nil, nil 584 } 585 if len(req.Roles) == 0 { 586 return nil, nil, errors.New("at least one role is required to add a permission (choices: read, write, owner, member)") 587 } 588 if slices.Contains(req.Roles, api.OwnerRole) { 589 fs.Debugf(m.remote, "skipping add permission -- can't invite a user with 'owner' role") 590 return nil, nil, nil 591 } 592 593 newP := &api.PermissionsResponse{} 594 err = m.fs.pacer.Call(func() (bool, error) { 595 resp, err = m.fs.srv.CallJSON(ctx, &opts, &req, &newP) 596 return shouldRetry(ctx, resp, err) 597 }) 598 599 return newP.Value, resp, err 600 } 601 602 // updatePermission updates an existing permission on an object or dir. 603 // This requires the permission ID and a role to update (which will error if it is the same as the existing role.) 604 // Role is the only property that can be updated. 605 func (m *Metadata) updatePermission(ctx context.Context, p *api.PermissionsType) (newP *api.PermissionsType, resp *http.Response, err error) { 606 opts := m.fs.newOptsCall(m.normalizedID, "PATCH", "/permissions/"+p.ID) 607 req := api.UpdatePermissionsRequest{Roles: p.Roles} // roles is the only property that can be updated 608 609 if len(req.Roles) == 0 { 610 return nil, nil, errors.New("at least one role is required to update a permission (choices: read, write, owner, member)") 611 } 612 613 newP = &api.PermissionsType{} 614 err = m.fs.pacer.Call(func() (bool, error) { 615 resp, err = m.fs.srv.CallJSON(ctx, &opts, &req, &newP) 616 return shouldRetry(ctx, resp, err) 617 }) 618 619 return newP, resp, err 620 } 621 622 // removePermission removes an existing permission on an object or dir. 623 // This requires the permission ID. 624 func (m *Metadata) removePermission(ctx context.Context, p *api.PermissionsType) (resp *http.Response, err error) { 625 opts := m.fs.newOptsCall(m.normalizedID, "DELETE", "/permissions/"+p.ID) 626 opts.NoResponse = true 627 628 err = m.fs.pacer.Call(func() (bool, error) { 629 resp, err = m.fs.srv.CallJSON(ctx, &opts, nil, nil) 630 return shouldRetry(ctx, resp, err) 631 }) 632 return resp, err 633 } 634 635 // getPermissions gets the current permissions for an object or dir, from the API. 636 func (f *Fs) getPermissions(ctx context.Context, normalizedID string) (p []*api.PermissionsType, resp *http.Response, err error) { 637 opts := f.newOptsCall(normalizedID, "GET", "/permissions") 638 639 permResp := &api.PermissionsResponse{} 640 err = f.pacer.Call(func() (bool, error) { 641 resp, err = f.srv.CallJSON(ctx, &opts, nil, &permResp) 642 return shouldRetry(ctx, resp, err) 643 }) 644 645 return permResp.Value, resp, err 646 } 647 648 func (f *Fs) newMetadata(remote string) *Metadata { 649 return &Metadata{fs: f, remote: remote} 650 } 651 652 // returns true if metadata includes a "permissions" key and f.opt.MetadataPermissions includes "write". 653 func (f *Fs) needsUpdatePermissions(metadata fs.Metadata) bool { 654 _, ok := metadata["permissions"] 655 return ok && f.opt.MetadataPermissions.IsSet(rwWrite) 656 } 657 658 // returns a non-zero btime if we have one 659 // otherwise falls back to mtime 660 func (o *Object) tryGetBtime(modTime time.Time) time.Time { 661 if o.meta != nil && !o.meta.btime.IsZero() { 662 return o.meta.btime 663 } 664 return modTime 665 } 666 667 // adds metadata (except permissions) if --metadata is in use 668 func (o *Object) fetchMetadataForCreate(ctx context.Context, src fs.ObjectInfo, options []fs.OpenOption, modTime time.Time) (createRequest api.CreateUploadRequest, metadata fs.Metadata, err error) { 669 createRequest = api.CreateUploadRequest{ // we set mtime no matter what 670 Item: api.Metadata{ 671 FileSystemInfo: &api.FileSystemInfoFacet{ 672 CreatedDateTime: api.Timestamp(o.tryGetBtime(modTime)), 673 LastModifiedDateTime: api.Timestamp(modTime), 674 }, 675 }, 676 } 677 678 meta, err := fs.GetMetadataOptions(ctx, o.fs, src, options) 679 if err != nil { 680 return createRequest, nil, fmt.Errorf("failed to read metadata from source object: %w", err) 681 } 682 if meta == nil { 683 return createRequest, nil, nil // no metadata or --metadata not in use, so just return mtime 684 } 685 if o.meta == nil { 686 o.meta = o.fs.newMetadata(o.Remote()) 687 } 688 o.meta.mtime = modTime 689 numSet, err := o.meta.Set(ctx, meta) 690 if err != nil { 691 return createRequest, meta, err 692 } 693 if numSet == 0 { 694 return createRequest, meta, nil 695 } 696 createRequest.Item = o.meta.toAPIMetadata() 697 return createRequest, meta, nil 698 } 699 700 // Fetch metadata and update updateInfo if --metadata is in use 701 // modtime will still be set when there is no metadata to set 702 func (f *Fs) fetchAndUpdateMetadata(ctx context.Context, src fs.ObjectInfo, options []fs.OpenOption, updateInfo *Object) (info *api.Item, err error) { 703 meta, err := fs.GetMetadataOptions(ctx, f, src, options) 704 if err != nil { 705 return nil, fmt.Errorf("failed to read metadata from source object: %w", err) 706 } 707 if meta == nil { 708 return updateInfo.setModTime(ctx, src.ModTime(ctx)) // no metadata or --metadata not in use, so just set modtime 709 } 710 if updateInfo.meta == nil { 711 updateInfo.meta = f.newMetadata(updateInfo.Remote()) 712 } 713 newInfo, err := updateInfo.updateMetadata(ctx, meta) 714 if newInfo == nil { 715 return info, err 716 } 717 return newInfo, err 718 } 719 720 // updateMetadata calls Get, Set, and Write 721 func (o *Object) updateMetadata(ctx context.Context, meta fs.Metadata) (info *api.Item, err error) { 722 _, err = o.meta.Get(ctx) // refresh permissions 723 if err != nil { 724 return nil, err 725 } 726 727 numSet, err := o.meta.Set(ctx, meta) 728 if err != nil { 729 return nil, err 730 } 731 if numSet == 0 { 732 return nil, nil 733 } 734 info, err = o.meta.Write(ctx, o.fs.needsUpdatePermissions(meta)) 735 if err != nil { 736 return info, err 737 } 738 err = o.setMetaData(info) 739 if err != nil { 740 return info, err 741 } 742 743 // Remove versions if required 744 if o.fs.opt.NoVersions { 745 err := o.deleteVersions(ctx) 746 if err != nil { 747 return info, fmt.Errorf("%v: Failed to remove versions: %v", o, err) 748 } 749 } 750 return info, nil 751 } 752 753 // MkdirMetadata makes the directory passed in as dir. 754 // 755 // It shouldn't return an error if it already exists. 756 // 757 // If the metadata is not nil it is set. 758 // 759 // It returns the directory that was created. 760 func (f *Fs) MkdirMetadata(ctx context.Context, dir string, metadata fs.Metadata) (fs.Directory, error) { 761 var info *api.Item 762 var meta *Metadata 763 dirID, err := f.dirCache.FindDir(ctx, dir, false) 764 if err == fs.ErrorDirNotFound { 765 // Directory does not exist so create it 766 var leaf, parentID string 767 leaf, parentID, err = f.dirCache.FindPath(ctx, dir, true) 768 if err != nil { 769 return nil, err 770 } 771 info, meta, err = f.createDir(ctx, parentID, dir, leaf, metadata) 772 if err != nil { 773 return nil, err 774 } 775 if f.driveType != driveTypePersonal { 776 // for some reason, OneDrive Business needs this extra step to set modtime, while Personal does not. Seems like a bug... 777 fs.Debugf(dir, "setting time %v", meta.mtime) 778 info, err = meta.Write(ctx, false) 779 } 780 } else if err == nil { 781 // Directory exists and needs updating 782 info, meta, err = f.updateDir(ctx, dirID, dir, metadata) 783 } 784 if err != nil { 785 return nil, err 786 } 787 788 // Convert the info into a directory entry 789 parent, _ := dircache.SplitPath(dir) 790 entry, err := f.itemToDirEntry(ctx, parent, info) 791 if err != nil { 792 return nil, err 793 } 794 directory, ok := entry.(*Directory) 795 if !ok { 796 return nil, fmt.Errorf("internal error: expecting %T to be a *Directory", entry) 797 } 798 directory.meta = meta 799 f.setSystemMetadata(info, directory.meta, entry.Remote(), dirMimeType) 800 801 dirEntry, ok := entry.(fs.Directory) 802 if !ok { 803 return nil, fmt.Errorf("internal error: expecting %T to be an fs.Directory", entry) 804 } 805 806 return dirEntry, nil 807 } 808 809 // createDir makes a directory with pathID as parent and name leaf with optional metadata 810 func (f *Fs) createDir(ctx context.Context, pathID, dirWithLeaf, leaf string, metadata fs.Metadata) (info *api.Item, meta *Metadata, err error) { 811 // fs.Debugf(f, "CreateDir(%q, %q)\n", dirID, leaf) 812 var resp *http.Response 813 opts := f.newOptsCall(pathID, "POST", "/children") 814 815 mkdir := api.CreateItemWithMetadataRequest{ 816 CreateItemRequest: api.CreateItemRequest{ 817 Name: f.opt.Enc.FromStandardName(leaf), 818 ConflictBehavior: "fail", 819 }, 820 } 821 m := f.newMetadata(dirWithLeaf) 822 m.mimeType = dirMimeType 823 numSet := 0 824 if len(metadata) > 0 { 825 826 numSet, err = m.Set(ctx, metadata) 827 if err != nil { 828 return nil, m, err 829 } 830 if numSet > 0 { 831 mkdir.Metadata = m.toAPIMetadata() 832 } 833 } 834 835 err = f.pacer.Call(func() (bool, error) { 836 resp, err = f.srv.CallJSON(ctx, &opts, &mkdir, &info) 837 return shouldRetry(ctx, resp, err) 838 }) 839 if err != nil { 840 return nil, m, err 841 } 842 843 if f.needsUpdatePermissions(metadata) && numSet > 0 { // permissions must be done as a separate step 844 m.normalizedID = info.GetID() 845 err = m.RefreshPermissions(ctx) 846 if err != nil { 847 return info, m, err 848 } 849 850 err = m.WritePermissions(ctx) 851 if err != nil { 852 fs.Errorf(m.remote, "error writing permissions: %v", err) 853 return info, m, err 854 } 855 } 856 return info, m, nil 857 } 858 859 // updateDir updates an existing a directory with the metadata passed in 860 func (f *Fs) updateDir(ctx context.Context, dirID, remote string, metadata fs.Metadata) (info *api.Item, meta *Metadata, err error) { 861 d := f.newDir(dirID, remote) 862 _, err = d.meta.Set(ctx, metadata) 863 if err != nil { 864 return nil, nil, err 865 } 866 info, err = d.meta.Write(ctx, f.needsUpdatePermissions(metadata)) 867 return info, d.meta, err 868 } 869 870 func (f *Fs) newDir(dirID, remote string) (d *Directory) { 871 d = &Directory{ 872 fs: f, 873 remote: remote, 874 size: -1, 875 items: -1, 876 id: dirID, 877 meta: f.newMetadata(remote), 878 } 879 d.meta.normalizedID = dirID 880 return d 881 } 882 883 // Metadata returns metadata for a DirEntry 884 // 885 // It should return nil if there is no Metadata 886 func (o *Object) Metadata(ctx context.Context) (metadata fs.Metadata, err error) { 887 err = o.readMetaData(ctx) 888 if err != nil { 889 fs.Logf(o, "Failed to read metadata: %v", err) 890 return nil, err 891 } 892 return o.meta.Get(ctx) 893 } 894 895 // DirSetModTime sets the directory modtime for dir 896 func (f *Fs) DirSetModTime(ctx context.Context, dir string, modTime time.Time) error { 897 dirID, err := f.dirCache.FindDir(ctx, dir, false) 898 if err != nil { 899 return err 900 } 901 d := f.newDir(dirID, dir) 902 return d.SetModTime(ctx, modTime) 903 } 904 905 // SetModTime sets the metadata on the DirEntry to set the modification date 906 // 907 // If there is any other metadata it does not overwrite it. 908 func (d *Directory) SetModTime(ctx context.Context, t time.Time) error { 909 btime := t 910 if d.meta != nil && !d.meta.btime.IsZero() { 911 btime = d.meta.btime // if we already have a non-zero btime, preserve it 912 } 913 d.meta = d.fs.newMetadata(d.remote) // set only the mtime and btime 914 d.meta.mtime = t 915 d.meta.btime = btime 916 _, err := d.meta.Write(ctx, false) 917 return err 918 } 919 920 // Metadata returns metadata for a DirEntry 921 // 922 // It should return nil if there is no Metadata 923 func (d *Directory) Metadata(ctx context.Context) (metadata fs.Metadata, err error) { 924 return d.meta.Get(ctx) 925 } 926 927 // SetMetadata sets metadata for a Directory 928 // 929 // It should return fs.ErrorNotImplemented if it can't set metadata 930 func (d *Directory) SetMetadata(ctx context.Context, metadata fs.Metadata) error { 931 _, meta, err := d.fs.updateDir(ctx, d.id, d.remote, metadata) 932 d.meta = meta 933 return err 934 } 935 936 // Fs returns read only access to the Fs that this object is part of 937 func (d *Directory) Fs() fs.Info { 938 return d.fs 939 } 940 941 // String returns the name 942 func (d *Directory) String() string { 943 return d.remote 944 } 945 946 // Remote returns the remote path 947 func (d *Directory) Remote() string { 948 return d.remote 949 } 950 951 // ModTime returns the modification date of the file 952 // 953 // If one isn't available it returns the configured --default-dir-time 954 func (d *Directory) ModTime(ctx context.Context) time.Time { 955 if !d.meta.mtime.IsZero() { 956 return d.meta.mtime 957 } 958 ci := fs.GetConfig(ctx) 959 return time.Time(ci.DefaultTime) 960 } 961 962 // Size returns the size of the file 963 func (d *Directory) Size() int64 { 964 return d.size 965 } 966 967 // Items returns the count of items in this directory or this 968 // directory and subdirectories if known, -1 for unknown 969 func (d *Directory) Items() int64 { 970 return d.items 971 } 972 973 // ID gets the optional ID 974 func (d *Directory) ID() string { 975 return d.id 976 } 977 978 // MimeType returns the content type of the Object if 979 // known, or "" if not 980 func (d *Directory) MimeType(ctx context.Context) string { 981 return dirMimeType 982 }