package selectors import ( "context" "fmt" "github.com/alcionai/clues" "github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/backup/identity" "github.com/alcionai/corso/src/pkg/dttm" "github.com/alcionai/corso/src/pkg/fault" "github.com/alcionai/corso/src/pkg/filters" "github.com/alcionai/corso/src/pkg/path" ) // --------------------------------------------------------------------------- // Selectors // --------------------------------------------------------------------------- type ( // groups provides an api for selecting // data scopes applicable to the groups service. groups struct { Selector } // groups provides an api for selecting // data scopes applicable to the groups service, // plus backup-specific methods. GroupsBackup struct { groups } // GroupsRestorep provides an api for selecting // data scopes applicable to the Groups service, // plus restore-specific methods. GroupsRestore struct { groups } ) var ( _ Reducer = &GroupsRestore{} _ pathCategorier = &GroupsRestore{} _ reasoner = &GroupsRestore{} ) // NewGroupsBackup produces a new Selector with the service set to ServiceGroups. func NewGroupsBackup(resources []string) *GroupsBackup { src := GroupsBackup{ groups{ newSelector(ServiceGroups, resources), }, } return &src } // ToGroupsBackup transforms the generic selector into an GroupsBackup. // Errors if the service defined by the selector is not ServiceGroups. func (s Selector) ToGroupsBackup() (*GroupsBackup, error) { if s.Service != ServiceGroups { return nil, badCastErr(ServiceGroups, s.Service) } src := GroupsBackup{groups{s}} return &src, nil } func (s GroupsBackup) SplitByResourceOwner(resources []string) []GroupsBackup { sels := splitByProtectedResource[GroupsScope](s.Selector, resources, GroupsGroup) ss := make([]GroupsBackup, 0, len(sels)) for _, sel := range sels { ss = append(ss, GroupsBackup{groups{sel}}) } return ss } // NewGroupsRestore produces a new Selector with the service set to ServiceGroups. func NewGroupsRestore(resources []string) *GroupsRestore { src := GroupsRestore{ groups{ newSelector(ServiceGroups, resources), }, } return &src } // ToGroupsRestore transforms the generic selector into an GroupsRestore. // Errors if the service defined by the selector is not ServiceGroups. func (s Selector) ToGroupsRestore() (*GroupsRestore, error) { if s.Service != ServiceGroups { return nil, badCastErr(ServiceGroups, s.Service) } src := GroupsRestore{groups{s}} return &src, nil } func (s GroupsRestore) SplitByResourceOwner(resources []string) []GroupsRestore { sels := splitByProtectedResource[GroupsScope](s.Selector, resources, GroupsGroup) ss := make([]GroupsRestore, 0, len(sels)) for _, sel := range sels { ss = append(ss, GroupsRestore{groups{sel}}) } return ss } // PathCategories produces the aggregation of discrete resources described by each type of scope. func (s groups) PathCategories() selectorPathCategories { return selectorPathCategories{ Excludes: pathCategoriesIn[GroupsScope, groupsCategory](s.Excludes), Filters: pathCategoriesIn[GroupsScope, groupsCategory](s.Filters), Includes: pathCategoriesIn[GroupsScope, groupsCategory](s.Includes), } } // Reasons returns a deduplicated set of the backup reasons produced // using the selector's discrete owner and each scopes' service and // category types. func (s groups) Reasons(tenantID string, useOwnerNameForID bool) []identity.Reasoner { return reasonsFor(s, tenantID, useOwnerNameForID) } // --------------------------------------------------------------------------- // Stringers and Concealers // --------------------------------------------------------------------------- func (s GroupsScope) Conceal() string { return conceal(s) } func (s GroupsScope) Format(fs fmt.State, r rune) { format(s, fs, r) } func (s GroupsScope) String() string { return conceal(s) } func (s GroupsScope) PlainString() string { return plainString(s) } // ------------------- // Scope Factories // Include appends the provided scopes to the selector's inclusion set. // Data is included if it matches ANY inclusion. // The inclusion set is later filtered (all included data must pass ALL // filters) and excluded (all included data must not match ANY exclusion). // Data is included if it matches ANY inclusion (of the same data category). // // All parts of the scope must match for data to be exclucded. // Ex: File(s1, f1, i1) => only excludes an item if it is owned by site s1, // located in folder f1, and ID'd as i1. Use selectors.Any() to wildcard // a scope value. No value will match if selectors.None() is provided. // // Group-level scopes will automatically apply the Any() wildcard to // child properties. // ex: Site(u1) automatically cascades to all folders and files owned // by s1. func (s *groups) Include(scopes ...[]GroupsScope) { s.Includes = appendScopes(s.Includes, scopes...) } // Exclude appends the provided scopes to the selector's exclusion set. // Every Exclusion scope applies globally, affecting all inclusion scopes. // Data is excluded if it matches ANY exclusion. // // All parts of the scope must match for data to be exclucded. // Ex: File(s1, f1, i1) => only excludes an item if it is owned by site s1, // located in folder f1, and ID'd as i1. Use selectors.Any() to wildcard // a scope value. No value will match if selectors.None() is provided. // // Group-level scopes will automatically apply the Any() wildcard to // child properties. // ex: Site(u1) automatically cascades to all folders and files owned // by s1. func (s *groups) Exclude(scopes ...[]GroupsScope) { s.Excludes = appendScopes(s.Excludes, scopes...) } // Filter appends the provided scopes to the selector's filters set. // A selector with >0 filters and 0 inclusions will include any data // that passes all filters. // A selector with >0 filters and >0 inclusions will reduce the // inclusion set to only the data that passes all filters. // Data is retained if it passes ALL filters. // // All parts of the scope must match for data to be exclucded. // Ex: File(s1, f1, i1) => only excludes an item if it is owned by site s1, // located in folder f1, and ID'd as i1. Use selectors.Any() to wildcard // a scope value. No value will match if selectors.None() is provided. // // Group-level scopes will automatically apply the Any() wildcard to // child properties. // ex: Site(u1) automatically cascades to all folders and files owned // by s1. func (s *groups) Filter(scopes ...[]GroupsScope) { s.Filters = appendScopes(s.Filters, scopes...) } // Scopes retrieves the list of groupsScopes in the selector. func (s *groups) Scopes() []GroupsScope { return scopes[GroupsScope](s.Selector) } // ------------------- // Scope Factories // Produces one or more Groups scopes. // One scope is created per group entry. // If any slice contains selectors.Any, that slice is reduced to [selectors.Any] // If any slice contains selectors.None, that slice is reduced to [selectors.None] // If any slice is empty, it defaults to [selectors.None] func (s *groups) AllData() []GroupsScope { scopes := []GroupsScope{} scopes = append( scopes, makeScope[GroupsScope](GroupsLibraryFolder, Any()), makeScope[GroupsScope](GroupsChannel, Any())) // TODO: enable conversations in all-data backups // makeScope[GroupsScope](GroupsConversation, Any())) return scopes } // Channels produces one or more SharePoint channel scopes, where the channel // matches upon a given channel by ID or Name. // If any slice contains selectors.Any, that slice is reduced to [selectors.Any] // If any slice contains selectors.None, that slice is reduced to [selectors.None] // If any slice is empty, it defaults to [selectors.None] func (s *groups) Channels(channels []string, opts ...option) []GroupsScope { var ( scopes = []GroupsScope{} os = append([]option{pathComparator()}, opts...) ) scopes = append( scopes, makeScope[GroupsScope](GroupsChannel, channels, os...)) return scopes } // ChannelMessages produces one or more Groups channel message scopes. // If any slice contains selectors.Any, that slice is reduced to [selectors.Any] // If any slice contains selectors.None, that slice is reduced to [selectors.None] // If any slice is empty, it defaults to [selectors.None] func (s *groups) ChannelMessages(channels, messages []string, opts ...option) []GroupsScope { var ( scopes = []GroupsScope{} os = append([]option{pathComparator()}, opts...) ) scopes = append( scopes, makeScope[GroupsScope](GroupsChannelMessage, messages, os...). set(GroupsChannel, channels, opts...)) return scopes } // Conversations produces one or more SharePoint conversation scopes, where the // conversation matches with a given conversation by ID or Topic. // If any slice contains selectors.Any, that slice is reduced to [selectors.Any] // If any slice contains selectors.None, that slice is reduced to [selectors.None] // If any slice is empty, it defaults to [selectors.None] func (s *groups) Conversation(conversations []string, opts ...option) []GroupsScope { var ( scopes = []GroupsScope{} os = append([]option{pathComparator()}, opts...) ) scopes = append( scopes, makeScope[GroupsScope](GroupsConversation, conversations, os...)) return scopes } // ConversationPosts produces one or more Groups conversation post scopes. // If any slice contains selectors.Any, that slice is reduced to [selectors.Any] // If any slice contains selectors.None, that slice is reduced to [selectors.None] // If any slice is empty, it defaults to [selectors.None] func (s *groups) ConversationPosts(conversations, posts []string, opts ...option) []GroupsScope { var ( scopes = []GroupsScope{} os = append([]option{pathComparator()}, opts...) ) scopes = append( scopes, makeScope[GroupsScope](GroupsConversationPost, posts, os...). set(GroupsConversation, conversations, opts...)) return scopes } // Sites produces one or more Groups site scopes, where the site // matches upon a given site by ID or URL. // If any slice contains selectors.Any, that slice is reduced to [selectors.Any] // If any slice contains selectors.None, that slice is reduced to [selectors.None] // If any slice is empty, it defaults to [selectors.None] func (s *groups) Site(site string) []GroupsScope { return []GroupsScope{ makeInfoScope[GroupsScope]( GroupsLibraryItem, GroupsInfoSite, []string{site}, filters.Equal), } } // Library produces one or more Group library scopes, where the library // matches upon a given drive by ID or Name. In order to ensure library selection // this should always be embedded within the Filter() set; include(Library()) will // select all items in the library without further filtering. // If any slice contains selectors.Any, that slice is reduced to [selectors.Any] // If any slice contains selectors.None, that slice is reduced to [selectors.None] // If any slice is empty, it defaults to [selectors.None] func (s *groups) Library(library string) []GroupsScope { return []GroupsScope{ makeInfoScope[GroupsScope]( GroupsLibraryItem, GroupsInfoSiteLibraryDrive, []string{library}, filters.Equal), } } // LibraryFolders produces one or more SharePoint libraryFolder scopes. // If any slice contains selectors.Any, that slice is reduced to [selectors.Any] // If any slice contains selectors.None, that slice is reduced to [selectors.None] // If any slice is empty, it defaults to [selectors.None] func (s *groups) LibraryFolders(libraryFolders []string, opts ...option) []GroupsScope { var ( scopes = []GroupsScope{} os = append([]option{pathComparator()}, opts...) ) scopes = append( scopes, makeScope[GroupsScope](GroupsLibraryFolder, libraryFolders, os...)) return scopes } // LibraryItems produces one or more Groups library item scopes. // If any slice contains selectors.Any, that slice is reduced to [selectors.Any] // If any slice contains selectors.None, that slice is reduced to [selectors.None] // If any slice is empty, it defaults to [selectors.None] // options are only applied to the library scopes. func (s *groups) LibraryItems(libraries, items []string, opts ...option) []GroupsScope { scopes := []GroupsScope{} scopes = append( scopes, makeScope[GroupsScope](GroupsLibraryItem, items, defaultItemOptions(s.Cfg)...). set(GroupsLibraryFolder, libraries, opts...)) return scopes } // Lists produces one or more Groups list scopes. // If any slice contains selectors.Any, that slice is reduced to [selectors.Any] // If any slice contains selectors.None, that slice is reduced to [selectors.None] // Any empty slice defaults to [selectors.None] func (s *groups) Lists(lists []string, opts ...option) []GroupsScope { var ( scopes = []GroupsScope{} os = append([]option{pathComparator()}, opts...) ) scopes = append(scopes, makeScope[GroupsScope](GroupsList, lists, os...)) return scopes } // ListItems produces one or more Groups list item scopes. // If any slice contains selectors.Any, that slice is reduced to [selectors.Any] // If any slice contains selectors.None, that slice is reduced to [selectors.None] // If any slice is empty, it defaults to [selectors.None] // options are only applied to the list scopes. func (s *groups) ListItems(lists, items []string, opts ...option) []GroupsScope { scopes := []GroupsScope{} scopes = append( scopes, makeScope[GroupsScope](GroupsListItem, items, defaultItemOptions(s.Cfg)...). set(GroupsList, lists, opts...)) return scopes } // Pages produces one or more Groups page scopes. // If any slice contains selectors.Any, that slice is reduced to [selectors.Any] // If any slice contains selectors.None, that slice is reduced to [selectors.None] // If any slice is empty, it defaults to [selectors.None] func (s *groups) Pages(pages []string, opts ...option) []GroupsScope { var ( scopes = []GroupsScope{} os = append([]option{pathComparator()}, opts...) ) scopes = append(scopes, makeScope[GroupsScope](GroupsPageFolder, pages, os...)) return scopes } // PageItems produces one or more Groups page item scopes. // If any slice contains selectors.Any, that slice is reduced to [selectors.Any] // If any slice contains selectors.None, that slice is reduced to [selectors.None] // If any slice is empty, it defaults to [selectors.None] // options are only applied to the page scopes. func (s *groups) PageItems(pages, items []string, opts ...option) []GroupsScope { scopes := []GroupsScope{} scopes = append( scopes, makeScope[GroupsScope](GroupsPage, items). set(GroupsPageFolder, pages, opts...)) return scopes } // ------------------- // ItemInfo Factories func (s *groups) CreatedAfter(timeStrings string) []GroupsScope { return []GroupsScope{ makeInfoScope[GroupsScope]( GroupsLibraryItem, GroupsInfoLibraryItemCreatedAfter, []string{timeStrings}, filters.Less), } } func (s *groups) CreatedBefore(timeStrings string) []GroupsScope { return []GroupsScope{ makeInfoScope[GroupsScope]( GroupsLibraryItem, GroupsInfoLibraryItemCreatedBefore, []string{timeStrings}, filters.Greater), } } func (s *groups) ModifiedAfter(timeStrings string) []GroupsScope { return []GroupsScope{ makeInfoScope[GroupsScope]( GroupsLibraryItem, GroupsInfoLibraryItemModifiedAfter, []string{timeStrings}, filters.Less), } } func (s *groups) ModifiedBefore(timeStrings string) []GroupsScope { return []GroupsScope{ makeInfoScope[GroupsScope]( GroupsLibraryItem, GroupsInfoLibraryItemModifiedBefore, []string{timeStrings}, filters.Greater), } } // MessageCreator produces one or more groups channelMessage info scopes. // Matches any channel message created by the specified user. // If any slice contains selectors.Any, that slice is reduced to [selectors.Any] // If any slice contains selectors.None, that slice is reduced to [selectors.None] // If any slice is empty, it defaults to [selectors.None] func (s *GroupsRestore) MessageCreator(creator string) []GroupsScope { return []GroupsScope{ makeInfoScope[GroupsScope]( GroupsChannelMessage, GroupsInfoChannelMessageCreator, []string{creator}, filters.In), } } // MessageCreatedAfter produces a channel message created-after info scope. // Matches any message where the creation time is after the timestring. // If the input equals selectors.Any, the scope will match all times. // If the input is empty or selectors.None, the scope will always fail comparisons. func (s *GroupsRestore) MessageCreatedAfter(timeStrings string) []GroupsScope { return []GroupsScope{ makeInfoScope[GroupsScope]( GroupsChannelMessage, GroupsInfoChannelMessageCreatedAfter, []string{timeStrings}, filters.Less), } } // MessageCreatedBefore produces a channel message created-before info scope. // Matches any message where the creation time is after the timestring. // If the input equals selectors.Any, the scope will match all times. // If the input is empty or selectors.None, the scope will always fail comparisons. func (s *GroupsRestore) MessageCreatedBefore(timeStrings string) []GroupsScope { return []GroupsScope{ makeInfoScope[GroupsScope]( GroupsChannelMessage, GroupsInfoChannelMessageCreatedBefore, []string{timeStrings}, filters.Greater), } } // MessageLastReplyAfter produces a channel message last-response-after info scope. // Matches any message where last response time is after the timestring. // If the input equals selectors.Any, the scope will match all times. // If the input is empty or selectors.None, the scope will always fail comparisons. func (s *GroupsRestore) MessageLastReplyAfter(timeStrings string) []GroupsScope { return []GroupsScope{ makeInfoScope[GroupsScope]( GroupsChannelMessage, GroupsInfoChannelMessageLastReplyAfter, []string{timeStrings}, filters.Less), } } // MessageLastReplyBefore produces a channel message last-response-before info scope. // Matches any message where last response time is after the timestring. // If the input equals selectors.Any, the scope will match all times. // If the input is empty or selectors.None, the scope will always fail comparisons. func (s *GroupsRestore) MessageLastReplyBefore(timeStrings string) []GroupsScope { return []GroupsScope{ makeInfoScope[GroupsScope]( GroupsChannelMessage, GroupsInfoChannelMessageLastReplyBefore, []string{timeStrings}, filters.Greater), } } // --------------------------------------------------------------------------- // Categories // --------------------------------------------------------------------------- // groupsCategory enumerates the type of the lowest level // of data () in a scope. type groupsCategory string // interface compliance checks var _ categorizer = GroupsCategoryUnknown const ( GroupsCategoryUnknown groupsCategory = "" // types of data in Groups GroupsGroup groupsCategory = "GroupsGroup" GroupsChannel groupsCategory = "GroupsChannel" GroupsChannelMessage groupsCategory = "GroupsChannelMessage" GroupsConversation groupsCategory = "GroupsConversation" GroupsConversationPost groupsCategory = "GroupsConversationPost" GroupsLibraryFolder groupsCategory = "GroupsLibraryFolder" GroupsLibraryItem groupsCategory = "GroupsLibraryItem" GroupsList groupsCategory = "GroupsList" GroupsListItem groupsCategory = "GroupsListItem" GroupsPageFolder groupsCategory = "GroupsPageFolder" GroupsPage groupsCategory = "GroupsPage" // details.itemInfo comparables GroupsInfoLibraryItemCreatedAfter groupsCategory = "GroupsInfoLibraryItemCreatedAfter" GroupsInfoLibraryItemCreatedBefore groupsCategory = "GroupsInfoLibraryItemCreatedBefore" GroupsInfoLibraryItemModifiedAfter groupsCategory = "GroupsInfoLibraryItemModifiedAfter" GroupsInfoLibraryItemModifiedBefore groupsCategory = "GroupsInfoLibraryItemModifiedBefore" // channel and drive selection GroupsInfoSite groupsCategory = "GroupsInfoSite" GroupsInfoSiteLibraryDrive groupsCategory = "GroupsInfoSiteLibraryDrive" // data contained within details.ItemInfo GroupsInfoChannelMessageCreatedAfter groupsCategory = "GroupsInfoChannelMessageCreatedAfter" GroupsInfoChannelMessageCreatedBefore groupsCategory = "GroupsInfoChannelMessageCreatedBefore" GroupsInfoChannelMessageCreator groupsCategory = "GroupsInfoChannelMessageCreator" GroupsInfoChannelMessageLastReplyAfter groupsCategory = "GroupsInfoChannelMessageLastReplyAfter" GroupsInfoChannelMessageLastReplyBefore groupsCategory = "GroupsInfoChannelMessageLastReplyBefore" ) // groupsLeafProperties describes common metadata of the leaf categories var groupsLeafProperties = map[categorizer]leafProperty{ GroupsChannelMessage: { pathKeys: []categorizer{GroupsChannel, GroupsChannelMessage}, pathType: path.ChannelMessagesCategory, }, GroupsConversationPost: { pathKeys: []categorizer{GroupsConversation, GroupsConversationPost}, pathType: path.ConversationPostsCategory, }, GroupsLibraryItem: { pathKeys: []categorizer{GroupsLibraryFolder, GroupsLibraryItem}, pathType: path.LibrariesCategory, }, GroupsGroup: { // the root category must be represented, even though it isn't a leaf pathKeys: []categorizer{GroupsGroup}, pathType: path.UnknownCategory, }, } func (c groupsCategory) String() string { return string(c) } // leafCat returns the leaf category of the receiver. // If the receiver category has multiple leaves (ex: User) or no leaves, // (ex: Unknown), the receiver itself is returned. // Ex: ServiceTypeFolder.leafCat() => ServiceTypeItem // Ex: ServiceUser.leafCat() => ServiceUser func (c groupsCategory) leafCat() categorizer { switch c { case GroupsChannel, GroupsChannelMessage, GroupsInfoChannelMessageCreatedAfter, GroupsInfoChannelMessageCreatedBefore, GroupsInfoChannelMessageCreator, GroupsInfoChannelMessageLastReplyAfter, GroupsInfoChannelMessageLastReplyBefore: return GroupsChannelMessage case GroupsConversation, GroupsConversationPost: return GroupsConversationPost case GroupsLibraryFolder, GroupsLibraryItem, GroupsInfoSite, GroupsInfoSiteLibraryDrive, GroupsInfoLibraryItemCreatedAfter, GroupsInfoLibraryItemCreatedBefore, GroupsInfoLibraryItemModifiedAfter, GroupsInfoLibraryItemModifiedBefore: return GroupsLibraryItem } return c } // rootCat returns the root category type. func (c groupsCategory) rootCat() categorizer { return GroupsGroup } // unknownCat returns the unknown category type. func (c groupsCategory) unknownCat() categorizer { return GroupsCategoryUnknown } // isUnion returns true if the category is a site or a webURL, which // can act as an alternative identifier to siteID across all site types. func (c groupsCategory) isUnion() bool { return c == c.rootCat() } // isLeaf is true if the category is a GroupsItem category. func (c groupsCategory) isLeaf() bool { return c == c.leafCat() } // pathValues transforms the two paths to maps of identified properties. // // Example: // [tenantID, service, groupID, site, siteID, category, folder, itemID] // => {spFolder: folder, spItemID: itemID} func (c groupsCategory) pathValues( repo path.Path, ent details.Entry, cfg Config, ) (map[categorizer][]string, error) { var ( folderCat, itemCat categorizer itemID string rFld string ) if ent.Groups == nil { return nil, clues.New("no Groups ItemInfo in details") } switch c { case GroupsChannel, GroupsChannelMessage: folderCat, itemCat = GroupsChannel, GroupsChannelMessage rFld = ent.Groups.ParentPath case GroupsConversation, GroupsConversationPost: folderCat, itemCat = GroupsConversation, GroupsConversationPost rFld = ent.Groups.ParentPath case GroupsLibraryFolder, GroupsLibraryItem: folderCat, itemCat = GroupsLibraryFolder, GroupsLibraryItem rFld = ent.Groups.ParentPath default: return nil, clues.New("unrecognized groupsCategory").With("category", c) } item := ent.ItemRef if len(item) == 0 { item = repo.Item() } if cfg.OnlyMatchItemNames { item = ent.ItemInfo.Groups.ItemName } result := map[categorizer][]string{ folderCat: {rFld}, itemCat: {item, ent.ShortRef}, } if len(itemID) > 0 { result[itemCat] = append(result[itemCat], itemID) } return result, nil } // pathKeys returns the path keys recognized by the receiver's leaf type. func (c groupsCategory) pathKeys() []categorizer { return groupsLeafProperties[c.leafCat()].pathKeys } // PathType converts the category's leaf type into the matching path.CategoryType. func (c groupsCategory) PathType() path.CategoryType { return groupsLeafProperties[c.leafCat()].pathType } // --------------------------------------------------------------------------- // Scopes // --------------------------------------------------------------------------- // GroupsScope specifies the data available // when interfacing with the Groups service. type GroupsScope scope // interface compliance checks var _ scoper = &GroupsScope{} // Category describes the type of the data in scope. func (s GroupsScope) Category() groupsCategory { return groupsCategory(getCategory(s)) } // categorizer type is a generic wrapper around Category. // Primarily used by scopes.go to for abstract comparisons. func (s GroupsScope) categorizer() categorizer { return s.Category() } // Matches returns true if the category is included in the scope's // data type, and the target string matches that category's comparator. func (s GroupsScope) Matches(cat groupsCategory, target string) bool { return matches(s, cat, target) } // InfoCategory returns the category enum of the scope info. // If the scope is not an info type, returns GroupsUnknownCategory. func (s GroupsScope) InfoCategory() groupsCategory { return groupsCategory(getInfoCategory(s)) } // IncludeCategory checks whether the scope includes a // certain category of data. // Ex: to check if the scope includes file data: // s.IncludesCategory(selector.GroupsFile) func (s GroupsScope) IncludesCategory(cat groupsCategory) bool { return categoryMatches(s.Category(), cat) } // returns true if the category is included in the scope's data type, // and the value is set to Any(). func (s GroupsScope) IsAny(cat groupsCategory) bool { return IsAnyTarget(s, cat) } // Get returns the data category in the scope. If the scope // contains all data types for a user, it'll return the // GroupsUser category. func (s GroupsScope) Get(cat groupsCategory) []string { return getCatValue(s, cat) } // sets a value by category to the scope. Only intended for internal use. func (s GroupsScope) set(cat groupsCategory, v []string, opts ...option) GroupsScope { os := []option{} switch cat { case GroupsChannel, GroupsConversation, GroupsLibraryFolder: os = append(os, pathComparator()) } return set(s, cat, v, append(os, opts...)...) } // setDefaults ensures that site scopes express `AnyTgt` for their child category types. func (s GroupsScope) setDefaults() { switch s.Category() { case GroupsGroup: s[GroupsChannel.String()] = passAny s[GroupsChannelMessage.String()] = passAny s[GroupsConversation.String()] = passAny s[GroupsConversationPost.String()] = passAny s[GroupsLibraryFolder.String()] = passAny s[GroupsLibraryItem.String()] = passAny case GroupsChannel: s[GroupsChannelMessage.String()] = passAny case GroupsLibraryFolder: s[GroupsLibraryItem.String()] = passAny case GroupsConversation: s[GroupsConversationPost.String()] = passAny } } // --------------------------------------------------------------------------- // Backup Details Filtering // --------------------------------------------------------------------------- // Reduce filters the entries in a details struct to only those that match the // inclusions, filters, and exclusions in the selector. func (s groups) Reduce( ctx context.Context, deets *details.Details, errs *fault.Bus, ) *details.Details { return reduce[GroupsScope]( ctx, deets, s.Selector, map[path.CategoryType]groupsCategory{ path.ChannelMessagesCategory: GroupsChannelMessage, path.ConversationPostsCategory: GroupsConversationPost, path.LibrariesCategory: GroupsLibraryItem, }, errs) } // matchesInfo handles the standard behavior when comparing a scope and an groupsInfo // returns true if the scope and info match for the provided category. func (s GroupsScope) matchesInfo(dii details.ItemInfo) bool { var ( infoCat = s.InfoCategory() i = "" info = dii.Groups ) if info == nil { return false } acceptableItemType := -1 switch infoCat.leafCat() { case GroupsLibraryItem: acceptableItemType = int(details.SharePointLibrary) case GroupsChannelMessage: acceptableItemType = int(details.GroupsChannelMessage) case GroupsConversationPost: acceptableItemType = int(details.GroupsConversationPost) } switch infoCat { case GroupsInfoSite: ds := []string{} if len(info.SiteID) > 0 { ds = append(ds, info.SiteID) } if len(info.WebURL) > 0 { ds = append(ds, info.WebURL) } return matchesAny(s, GroupsInfoSite, ds) case GroupsInfoSiteLibraryDrive: ds := []string{} if len(info.DriveName) > 0 { ds = append(ds, info.DriveName) } if len(info.DriveID) > 0 { ds = append(ds, info.DriveID) } return matchesAny(s, GroupsInfoSiteLibraryDrive, ds) case GroupsInfoLibraryItemCreatedAfter, GroupsInfoLibraryItemCreatedBefore: i = dttm.Format(info.Created) case GroupsInfoLibraryItemModifiedAfter, GroupsInfoLibraryItemModifiedBefore: i = dttm.Format(info.Modified) case GroupsInfoChannelMessageCreator: i = info.Message.Creator case GroupsInfoChannelMessageCreatedAfter, GroupsInfoChannelMessageCreatedBefore: i = dttm.Format(info.Message.CreatedAt) case GroupsInfoChannelMessageLastReplyAfter, GroupsInfoChannelMessageLastReplyBefore: if info.LastReply.CreatedAt.IsZero() { return false } i = dttm.Format(info.LastReply.CreatedAt) } return s.Matches(infoCat, i) && int(info.ItemType) == acceptableItemType }