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  }