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