# UNDF: UNDF-2026-000001025 --- a/backend/onedrive/metadata.go +++ b/backend/onedrive/metadata.go @@ -432,6 +432,24 @@ func (m *Metadata) sortPermissions() (add, update, remove []*api.PermissionsType new, old := m.queuedPermissions, m.permissions if len(old) == 0 || m.permsAddOnly { m.orderPermissions(new) return new, nil, nil // they must all be "add" } + // Build O(1) lookup maps by permission ID to avoid O(P^2) nested scans. + // sortPermissions is called once per file when --metadata is set; large + // SharePoint sites can carry hundreds of permissions per document, making + // the original slices.ContainsFunc / slices.Contains calls O(P^2). + oldByID := make(map[string]*api.PermissionsType, len(old)) + for _, o := range old { + if o != nil && o.ID != "" { + oldByID[o.ID] = o + } + } + newByID := make(map[string]*api.PermissionsType, len(new)) + for _, n := range new { + if n != nil && n.ID != "" { + newByID[n.ID] = n + } + } + addSet := make(map[string]struct{}, len(new)) + updateSet := make(map[string]struct{}, len(new)) + for _, n := range new { if n == nil { continue } if n.ID != "" { // sanity check: ensure there's a matching "old" id with a non-matching role - if !slices.ContainsFunc(old, func(o *api.PermissionsType) bool { - 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) - }) { + o, exists := oldByID[n.ID] + if !exists || !(slices.Compare(o.Roles, n.Roles) != 0 && len(o.Roles) > 0 && len(n.Roles) > 0 && !slices.Contains(o.Roles, api.OwnerRole)) { fs.Debugf(m.remote, "skipping update for invalid roles: %v (perm ID: %v)", n.Roles, n.ID) continue } @@ -462,12 +480,14 @@ func (m *Metadata) sortPermissions() (add, update, remove []*api.PermissionsType fs.Debugf(m.remote, "sortPermissions: will update role to %v", n.Roles) update = append(update, n) + updateSet[n.ID] = struct{}{} } else { fs.Debugf(m.remote, "sortPermissions: will add permission: %v %v", n, n.Roles) add = append(add, n) + addSet[n.ID] = struct{}{} } } for _, o := range old { if slices.Contains(o.Roles, api.OwnerRole) { fs.Debugf(m.remote, "skipping remove permission -- can't remove 'owner' role") continue } - newHasOld := slices.ContainsFunc(new, func(n *api.PermissionsType) bool { - if n == nil || n.ID == "" { - return false // can't remove perms without an ID - } - return n.ID == o.ID - }) - if !newHasOld && o.ID != "" && !slices.Contains(add, o) && !slices.Contains(update, o) { + _, newHasOld := newByID[o.ID] + _, inAdd := addSet[o.ID] + _, inUpdate := updateSet[o.ID] + if !newHasOld && o.ID != "" && !inAdd && !inUpdate { fs.Debugf(m.remote, "sortPermissions: will remove permission: %v %v (perm ID: %v)", o, o.Roles, o.ID) remove = append(remove, o) }