#### Does this PR need a docs update or release note? - [x] ⛔ No #### Type of change - [x] 🧹 Tech Debt/Cleanup #### Issue(s) * #3993 #### Test Plan - [x] ⚡ Unit test - [x] 💚 E2E
819 lines
28 KiB
Go
819 lines
28 KiB
Go
package selectors
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strconv"
|
|
|
|
"github.com/alcionai/clues"
|
|
|
|
"github.com/alcionai/corso/src/internal/common/dttm"
|
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
|
"github.com/alcionai/corso/src/pkg/backup/identity"
|
|
"github.com/alcionai/corso/src/pkg/fault"
|
|
"github.com/alcionai/corso/src/pkg/filters"
|
|
"github.com/alcionai/corso/src/pkg/path"
|
|
)
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Selectors
|
|
// ---------------------------------------------------------------------------
|
|
|
|
type (
|
|
// exchange provides an api for selecting
|
|
// data scopes applicable to the Exchange service.
|
|
exchange struct {
|
|
Selector
|
|
}
|
|
|
|
// ExchangeBackup provides an api for selecting
|
|
// data scopes applicable to the Exchange service,
|
|
// plus backup-specific methods.
|
|
ExchangeBackup struct {
|
|
exchange
|
|
}
|
|
|
|
// ExchangeRestore provides an api for selecting
|
|
// data scopes applicable to the Exchange service,
|
|
// plus restore-specific methods.
|
|
ExchangeRestore struct {
|
|
exchange
|
|
}
|
|
)
|
|
|
|
var (
|
|
_ Reducer = &ExchangeRestore{}
|
|
_ pathCategorier = &ExchangeRestore{}
|
|
_ reasoner = &ExchangeRestore{}
|
|
)
|
|
|
|
// NewExchange produces a new Selector with the service set to ServiceExchange.
|
|
func NewExchangeBackup(users []string) *ExchangeBackup {
|
|
src := ExchangeBackup{
|
|
exchange{
|
|
newSelector(ServiceExchange, users),
|
|
},
|
|
}
|
|
|
|
return &src
|
|
}
|
|
|
|
// ToExchangeBackup transforms the generic selector into an ExchangeBackup.
|
|
// Errors if the service defined by the selector is not ServiceExchange.
|
|
func (s Selector) ToExchangeBackup() (*ExchangeBackup, error) {
|
|
if s.Service != ServiceExchange {
|
|
return nil, badCastErr(ServiceExchange, s.Service)
|
|
}
|
|
|
|
src := ExchangeBackup{exchange{s}}
|
|
|
|
return &src, nil
|
|
}
|
|
|
|
func (s ExchangeBackup) SplitByResourceOwner(users []string) []ExchangeBackup {
|
|
sels := splitByProtectedResource[ExchangeScope](s.Selector, users, ExchangeUser)
|
|
|
|
ss := make([]ExchangeBackup, 0, len(sels))
|
|
for _, sel := range sels {
|
|
ss = append(ss, ExchangeBackup{exchange{sel}})
|
|
}
|
|
|
|
return ss
|
|
}
|
|
|
|
// NewExchangeRestore produces a new Selector with the service set to ServiceExchange.
|
|
func NewExchangeRestore(users []string) *ExchangeRestore {
|
|
src := ExchangeRestore{
|
|
exchange{
|
|
newSelector(ServiceExchange, users),
|
|
},
|
|
}
|
|
|
|
return &src
|
|
}
|
|
|
|
// ToExchangeRestore transforms the generic selector into an ExchangeRestore.
|
|
// Errors if the service defined by the selector is not ServiceExchange.
|
|
func (s Selector) ToExchangeRestore() (*ExchangeRestore, error) {
|
|
if s.Service != ServiceExchange {
|
|
return nil, badCastErr(ServiceExchange, s.Service)
|
|
}
|
|
|
|
src := ExchangeRestore{exchange{s}}
|
|
|
|
return &src, nil
|
|
}
|
|
|
|
func (sr ExchangeRestore) SplitByResourceOwner(users []string) []ExchangeRestore {
|
|
sels := splitByProtectedResource[ExchangeScope](sr.Selector, users, ExchangeUser)
|
|
|
|
ss := make([]ExchangeRestore, 0, len(sels))
|
|
for _, sel := range sels {
|
|
ss = append(ss, ExchangeRestore{exchange{sel}})
|
|
}
|
|
|
|
return ss
|
|
}
|
|
|
|
// PathCategories produces the aggregation of discrete users described by each type of scope.
|
|
func (s exchange) PathCategories() selectorPathCategories {
|
|
return selectorPathCategories{
|
|
Excludes: pathCategoriesIn[ExchangeScope, exchangeCategory](s.Excludes),
|
|
Filters: pathCategoriesIn[ExchangeScope, exchangeCategory](s.Filters),
|
|
Includes: pathCategoriesIn[ExchangeScope, exchangeCategory](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 exchange) Reasons(tenantID string, useOwnerNameForID bool) []identity.Reasoner {
|
|
return reasonsFor(s, tenantID, useOwnerNameForID)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Stringers and Concealers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func (s ExchangeScope) Conceal() string { return conceal(s) }
|
|
func (s ExchangeScope) Format(fs fmt.State, r rune) { format(s, fs, r) }
|
|
func (s ExchangeScope) String() string { return conceal(s) }
|
|
func (s ExchangeScope) PlainString() string { return plainString(s) }
|
|
|
|
// -------------------
|
|
// Exclude/Includes
|
|
|
|
// 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 (of the same data category).
|
|
//
|
|
// All parts of the scope must match for data to be exclucded.
|
|
// Ex: Mail(u1, f1, m1) => only excludes mail if it is owned by user u1,
|
|
// located in folder f1, and ID'd as m1. MailSender(foo) => only excludes
|
|
// mail whose sender is foo. 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: User(u1) automatically cascades to all mail, events, and contacts,
|
|
// therefore it is the same as selecting all of the following:
|
|
// Mail(u1, Any(), Any()), Event(u1, Any()), Contacts(u1, Any(), Any())
|
|
func (s *exchange) Exclude(scopes ...[]ExchangeScope) {
|
|
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 (of the same data category).
|
|
//
|
|
// All parts of the scope must match for data to pass the filter.
|
|
// Ex: Mail(u1, f1, m1) => only passes mail that is owned by user u1,
|
|
// located in folder f1, and ID'd as m1. MailSender(foo) => only passes
|
|
// mail whose sender is foo. 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: User(u1) automatically cascades to all mail, events, and contacts,
|
|
// therefore it is the same as selecting all of the following:
|
|
// Mail(u1, Any(), Any()), Event(u1, Any()), Contacts(u1, Any(), Any())
|
|
func (s *exchange) Filter(scopes ...[]ExchangeScope) {
|
|
s.Filters = appendScopes(s.Filters, scopes...)
|
|
}
|
|
|
|
// 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 included.
|
|
// Ex: Mail(u1, f1, m1) => only includes mail if it is owned by user u1,
|
|
// located in folder f1, and ID'd as m1. MailSender(foo) => only includes
|
|
// mail whose sender is foo. 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: User(u1) automatically cascades to all mail, events, and contacts,
|
|
// therefore it is the same as selecting all of the following:
|
|
// Mail(u1, Any(), Any()), Event(u1, Any()), Contacts(u1, Any(), Any())
|
|
func (s *exchange) Include(scopes ...[]ExchangeScope) {
|
|
s.Includes = appendScopes(s.Includes, scopes...)
|
|
}
|
|
|
|
// Scopes retrieves the list of exchangeScopes in the selector.
|
|
func (s *exchange) Scopes() []ExchangeScope {
|
|
return scopes[ExchangeScope](s.Selector)
|
|
}
|
|
|
|
type ExchangeItemScopeConstructor func([]string, []string, ...option) []ExchangeScope
|
|
|
|
// -------------------
|
|
// Scope Factories
|
|
|
|
// Contacts produces one or more exchange contact 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 folder scopes.
|
|
func (s *exchange) Contacts(folders, contacts []string, opts ...option) []ExchangeScope {
|
|
scopes := []ExchangeScope{}
|
|
|
|
scopes = append(
|
|
scopes,
|
|
makeScope[ExchangeScope](ExchangeContact, contacts, defaultItemOptions(s.Cfg)...).
|
|
set(ExchangeContactFolder, folders, opts...))
|
|
|
|
return scopes
|
|
}
|
|
|
|
// Contactfolders produces one or more exchange contact folder 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 folder scopes.
|
|
func (s *exchange) ContactFolders(folders []string, opts ...option) []ExchangeScope {
|
|
var (
|
|
scopes = []ExchangeScope{}
|
|
os = append([]option{pathComparator()}, opts...)
|
|
)
|
|
|
|
scopes = append(
|
|
scopes,
|
|
makeScope[ExchangeScope](ExchangeContactFolder, folders, os...))
|
|
|
|
return scopes
|
|
}
|
|
|
|
// Events produces one or more exchange event 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 folder scopes.
|
|
func (s *exchange) Events(calendars, events []string, opts ...option) []ExchangeScope {
|
|
scopes := []ExchangeScope{}
|
|
|
|
scopes = append(
|
|
scopes,
|
|
makeScope[ExchangeScope](ExchangeEvent, events, defaultItemOptions(s.Cfg)...).
|
|
set(ExchangeEventCalendar, calendars, opts...))
|
|
|
|
return scopes
|
|
}
|
|
|
|
// EventCalendars produces one or more exchange event calendar scopes.
|
|
// Calendars act as folders to contain Events
|
|
// 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 folder scopes.
|
|
func (s *exchange) EventCalendars(events []string, opts ...option) []ExchangeScope {
|
|
var (
|
|
scopes = []ExchangeScope{}
|
|
os = append([]option{pathComparator()}, opts...)
|
|
)
|
|
|
|
scopes = append(
|
|
scopes,
|
|
makeScope[ExchangeScope](ExchangeEventCalendar, events, os...))
|
|
|
|
return scopes
|
|
}
|
|
|
|
// Produces one or more mail 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 folder scopes.
|
|
func (s *exchange) Mails(folders, mails []string, opts ...option) []ExchangeScope {
|
|
scopes := []ExchangeScope{}
|
|
|
|
scopes = append(
|
|
scopes,
|
|
makeScope[ExchangeScope](ExchangeMail, mails, defaultItemOptions(s.Cfg)...).
|
|
set(ExchangeMailFolder, folders, opts...))
|
|
|
|
return scopes
|
|
}
|
|
|
|
// Produces one or more exchange mail folder 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 folder scopes.
|
|
func (s *exchange) MailFolders(folders []string, opts ...option) []ExchangeScope {
|
|
var (
|
|
scopes = []ExchangeScope{}
|
|
os = append([]option{pathComparator()}, opts...)
|
|
)
|
|
|
|
scopes = append(
|
|
scopes,
|
|
makeScope[ExchangeScope](ExchangeMailFolder, folders, os...))
|
|
|
|
return scopes
|
|
}
|
|
|
|
// Retrieves all exchange data.
|
|
// Each user id generates three scopes, one for each data type: contact, event, and mail.
|
|
// 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 *exchange) AllData() []ExchangeScope {
|
|
scopes := []ExchangeScope{}
|
|
|
|
scopes = append(scopes,
|
|
makeScope[ExchangeScope](ExchangeContactFolder, Any()),
|
|
makeScope[ExchangeScope](ExchangeEventCalendar, Any()),
|
|
makeScope[ExchangeScope](ExchangeMailFolder, Any()))
|
|
|
|
return scopes
|
|
}
|
|
|
|
// -------------------
|
|
// Info Factories
|
|
|
|
// ContactName produces one or more exchange contact name info scopes.
|
|
// Matches any contact whose name contains the provided string.
|
|
// 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 (sr *ExchangeRestore) ContactName(senderID string) []ExchangeScope {
|
|
return []ExchangeScope{
|
|
makeInfoScope[ExchangeScope](
|
|
ExchangeContact,
|
|
ExchangeInfoContactName,
|
|
[]string{senderID},
|
|
filters.In),
|
|
}
|
|
}
|
|
|
|
// EventSubject produces one or more exchange event subject info scopes.
|
|
// Matches any event where the event subject contains one of the provided strings.
|
|
// 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 (sr *ExchangeRestore) EventOrganizer(organizer string) []ExchangeScope {
|
|
return []ExchangeScope{
|
|
makeInfoScope[ExchangeScope](
|
|
ExchangeEvent,
|
|
ExchangeInfoEventOrganizer,
|
|
[]string{organizer},
|
|
filters.In),
|
|
}
|
|
}
|
|
|
|
// EventRecurs produces one or more exchange event recurrence info scopes.
|
|
// Matches any event if the comparator flag matches the event recurrence flag.
|
|
// 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 (sr *ExchangeRestore) EventRecurs(recurs string) []ExchangeScope {
|
|
return []ExchangeScope{
|
|
makeInfoScope[ExchangeScope](
|
|
ExchangeEvent,
|
|
ExchangeInfoEventRecurs,
|
|
[]string{recurs},
|
|
filters.Equal),
|
|
}
|
|
}
|
|
|
|
// EventStartsAfter produces an exchange event starts-after info scope.
|
|
// Matches any event where the start 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 (sr *ExchangeRestore) EventStartsAfter(timeStrings string) []ExchangeScope {
|
|
return []ExchangeScope{
|
|
makeInfoScope[ExchangeScope](
|
|
ExchangeEvent,
|
|
ExchangeInfoEventStartsAfter,
|
|
[]string{timeStrings},
|
|
filters.Less),
|
|
}
|
|
}
|
|
|
|
// EventStartsBefore produces an exchange event starts-before info scope.
|
|
// Matches any event where the start time is before 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 (sr *ExchangeRestore) EventStartsBefore(timeStrings string) []ExchangeScope {
|
|
return []ExchangeScope{
|
|
makeInfoScope[ExchangeScope](
|
|
ExchangeEvent,
|
|
ExchangeInfoEventStartsBefore,
|
|
[]string{timeStrings},
|
|
filters.Greater),
|
|
}
|
|
}
|
|
|
|
// EventSubject produces one or more exchange event subject info scopes.
|
|
// Matches any event where the event subject contains one of the provided strings.
|
|
// 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 (sr *ExchangeRestore) EventSubject(subject string) []ExchangeScope {
|
|
return []ExchangeScope{
|
|
makeInfoScope[ExchangeScope](
|
|
ExchangeEvent,
|
|
ExchangeInfoEventSubject,
|
|
[]string{subject},
|
|
filters.In),
|
|
}
|
|
}
|
|
|
|
// MailReceivedAfter produces an exchange mail received-after info scope.
|
|
// Matches any mail which was received 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 (sr *ExchangeRestore) MailReceivedAfter(timeStrings string) []ExchangeScope {
|
|
return []ExchangeScope{
|
|
makeInfoScope[ExchangeScope](
|
|
ExchangeMail,
|
|
ExchangeInfoMailReceivedAfter,
|
|
[]string{timeStrings},
|
|
filters.Less),
|
|
}
|
|
}
|
|
|
|
// MailReceivedBefore produces an exchange mail received-before info scope.
|
|
// Matches any mail which was received before 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 (sr *ExchangeRestore) MailReceivedBefore(timeStrings string) []ExchangeScope {
|
|
return []ExchangeScope{
|
|
makeInfoScope[ExchangeScope](
|
|
ExchangeMail,
|
|
ExchangeInfoMailReceivedBefore,
|
|
[]string{timeStrings},
|
|
filters.Greater),
|
|
}
|
|
}
|
|
|
|
// MailSender produces one or more exchange mail sender info scopes.
|
|
// Matches any mail whose sender contains one of the provided strings.
|
|
// 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 (sr *ExchangeRestore) MailSender(sender string) []ExchangeScope {
|
|
return []ExchangeScope{
|
|
makeInfoScope[ExchangeScope](
|
|
ExchangeMail,
|
|
ExchangeInfoMailSender,
|
|
[]string{sender},
|
|
filters.In),
|
|
}
|
|
}
|
|
|
|
// MailSubject produces one or more exchange mail subject line info scopes.
|
|
// Matches any mail whose subject contains one of the provided strings.
|
|
// 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 (sr *ExchangeRestore) MailSubject(subject string) []ExchangeScope {
|
|
return []ExchangeScope{
|
|
makeInfoScope[ExchangeScope](
|
|
ExchangeMail,
|
|
ExchangeInfoMailSubject,
|
|
[]string{subject},
|
|
filters.In),
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Categories
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// exchangeCategory enumerates the type of the lowest level
|
|
// of data specified by the scope.
|
|
type exchangeCategory string
|
|
|
|
// interface compliance checks
|
|
var _ categorizer = ExchangeCategoryUnknown
|
|
|
|
const (
|
|
ExchangeCategoryUnknown exchangeCategory = ""
|
|
|
|
// types of data identified by exchange
|
|
ExchangeContact exchangeCategory = "ExchangeContact"
|
|
ExchangeContactFolder exchangeCategory = "ExchangeContactFolder"
|
|
ExchangeEvent exchangeCategory = "ExchangeEvent"
|
|
ExchangeEventCalendar exchangeCategory = "ExchangeEventCalendar"
|
|
ExchangeMail exchangeCategory = "ExchangeMail"
|
|
ExchangeMailFolder exchangeCategory = "ExchangeMailFolder"
|
|
ExchangeUser exchangeCategory = "ExchangeUser"
|
|
|
|
// data contained within details.ItemInfo
|
|
ExchangeInfoMailSender exchangeCategory = "ExchangeInfoMailSender"
|
|
ExchangeInfoMailSubject exchangeCategory = "ExchangeInfoMailSubject"
|
|
ExchangeInfoMailReceivedAfter exchangeCategory = "ExchangeInfoMailReceivedAfter"
|
|
ExchangeInfoMailReceivedBefore exchangeCategory = "ExchangeInfoMailReceivedBefore"
|
|
ExchangeInfoContactName exchangeCategory = "ExchangeInfoContactName"
|
|
ExchangeInfoEventOrganizer exchangeCategory = "ExchangeInfoEventOrganizer"
|
|
ExchangeInfoEventRecurs exchangeCategory = "ExchangeInfoEventRecurs"
|
|
ExchangeInfoEventStartsAfter exchangeCategory = "ExchangeInfoEventStartsAfter"
|
|
ExchangeInfoEventStartsBefore exchangeCategory = "ExchangeInfoEventStartsBefore"
|
|
ExchangeInfoEventSubject exchangeCategory = "ExchangeInfoEventSubject"
|
|
)
|
|
|
|
// exchangeLeafProperties describes common metadata of the leaf categories
|
|
var exchangeLeafProperties = map[categorizer]leafProperty{
|
|
ExchangeContact: {
|
|
pathKeys: []categorizer{ExchangeContactFolder, ExchangeContact},
|
|
pathType: path.ContactsCategory,
|
|
},
|
|
ExchangeEvent: {
|
|
pathKeys: []categorizer{ExchangeEventCalendar, ExchangeEvent},
|
|
pathType: path.EventsCategory,
|
|
},
|
|
ExchangeMail: {
|
|
pathKeys: []categorizer{ExchangeMailFolder, ExchangeMail},
|
|
pathType: path.EmailCategory,
|
|
},
|
|
ExchangeUser: { // the root category must be represented, even though it isn't a leaf
|
|
pathKeys: []categorizer{ExchangeUser},
|
|
pathType: path.UnknownCategory,
|
|
},
|
|
}
|
|
|
|
func (ec exchangeCategory) String() string {
|
|
return string(ec)
|
|
}
|
|
|
|
// 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.
|
|
// If the receiver category is an info type (ex: ExchangeInfoMailSubject),
|
|
// returns the category covered by the info.
|
|
// Ex: ExchangeContactFolder.leafCat() => ExchangeContact
|
|
// Ex: ExchangeEvent.leafCat() => ExchangeEvent
|
|
// Ex: ExchangeUser.leafCat() => ExchangeUser
|
|
func (ec exchangeCategory) leafCat() categorizer {
|
|
switch ec {
|
|
case ExchangeContact, ExchangeContactFolder, ExchangeInfoContactName:
|
|
return ExchangeContact
|
|
|
|
case ExchangeEvent, ExchangeEventCalendar, ExchangeInfoEventOrganizer, ExchangeInfoEventRecurs,
|
|
ExchangeInfoEventStartsAfter, ExchangeInfoEventStartsBefore, ExchangeInfoEventSubject:
|
|
return ExchangeEvent
|
|
|
|
case ExchangeMail, ExchangeMailFolder, ExchangeInfoMailReceivedAfter,
|
|
ExchangeInfoMailReceivedBefore, ExchangeInfoMailSender, ExchangeInfoMailSubject:
|
|
return ExchangeMail
|
|
}
|
|
|
|
return ec
|
|
}
|
|
|
|
// rootCat returns the root category type.
|
|
func (ec exchangeCategory) rootCat() categorizer {
|
|
return ExchangeUser
|
|
}
|
|
|
|
// unknownCat returns the unknown category type.
|
|
func (ec exchangeCategory) unknownCat() categorizer {
|
|
return ExchangeCategoryUnknown
|
|
}
|
|
|
|
// isUnion returns true if c is a user
|
|
func (ec exchangeCategory) isUnion() bool {
|
|
return ec == ec.rootCat()
|
|
}
|
|
|
|
// isLeaf is true if the category is a mail, event, or contact category.
|
|
func (ec exchangeCategory) isLeaf() bool {
|
|
return ec == ec.leafCat()
|
|
}
|
|
|
|
// pathValues transforms the two paths to maps of identified properties.
|
|
//
|
|
// Example:
|
|
// [tenantID, service, userPN, category, mailFolder, mailID]
|
|
// => {exchMailFolder: mailFolder, exchMail: mailID}
|
|
func (ec exchangeCategory) pathValues(
|
|
repo path.Path,
|
|
ent details.Entry,
|
|
cfg Config,
|
|
) (map[categorizer][]string, error) {
|
|
var folderCat, itemCat categorizer
|
|
|
|
switch ec {
|
|
case ExchangeContact:
|
|
folderCat, itemCat = ExchangeContactFolder, ExchangeContact
|
|
|
|
case ExchangeEvent:
|
|
folderCat, itemCat = ExchangeEventCalendar, ExchangeEvent
|
|
|
|
case ExchangeMail:
|
|
folderCat, itemCat = ExchangeMailFolder, ExchangeMail
|
|
|
|
default:
|
|
return nil, clues.New("bad exchanageCategory").With("category", ec)
|
|
}
|
|
|
|
item := ent.ItemRef
|
|
if len(item) == 0 {
|
|
item = repo.Item()
|
|
}
|
|
|
|
items := []string{ent.ShortRef, item}
|
|
|
|
// only include the item ID when the user is NOT matching
|
|
// item names. Exchange data does not contain an item name,
|
|
// only an ID, and we don't want to mix up the two.
|
|
if cfg.OnlyMatchItemNames {
|
|
items = []string{ent.ShortRef}
|
|
}
|
|
|
|
// Will hit the if-condition when we're at a top-level folder, but we'll get
|
|
// the same result when we extract from the RepoRef.
|
|
folder := ent.LocationRef
|
|
if len(folder) == 0 {
|
|
folder = repo.Folder(true)
|
|
}
|
|
|
|
result := map[categorizer][]string{
|
|
folderCat: {folder},
|
|
itemCat: items,
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// pathKeys returns the path keys recognized by the receiver's leaf type.
|
|
func (ec exchangeCategory) pathKeys() []categorizer {
|
|
return exchangeLeafProperties[ec.leafCat()].pathKeys
|
|
}
|
|
|
|
// PathType converts the category's leaf type into the matching path.CategoryType.
|
|
func (ec exchangeCategory) PathType() path.CategoryType {
|
|
return exchangeLeafProperties[ec.leafCat()].pathType
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Scopes
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// ExchangeScope specifies the data available
|
|
// when interfacing with the Exchange service.
|
|
type ExchangeScope scope
|
|
|
|
// interface compliance checks
|
|
var _ scoper = &ExchangeScope{}
|
|
|
|
// Category describes the type of the data in scope.
|
|
func (s ExchangeScope) Category() exchangeCategory {
|
|
return exchangeCategory(getCategory(s))
|
|
}
|
|
|
|
// categorizer type is a generic wrapper around Category.
|
|
// Primarily used by scopes.go to for abstract comparisons.
|
|
func (s ExchangeScope) 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 ExchangeScope) Matches(cat exchangeCategory, 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 ExchangeUnknownCategory.
|
|
func (s ExchangeScope) InfoCategory() exchangeCategory {
|
|
return exchangeCategory(getInfoCategory(s))
|
|
}
|
|
|
|
// IncludeCategory checks whether the scope includes a certain category of data.
|
|
// Ex: to check if the scope includes mail data:
|
|
// s.IncludesCategory(selector.ExchangeMail)
|
|
func (s ExchangeScope) IncludesCategory(cat exchangeCategory) 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 ExchangeScope) IsAny(cat exchangeCategory) 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
|
|
// ExchangeUser category.
|
|
func (s ExchangeScope) Get(cat exchangeCategory) []string {
|
|
return getCatValue(s, cat)
|
|
}
|
|
|
|
// sets a value by category to the scope. Only intended for internal use.
|
|
func (s ExchangeScope) set(cat exchangeCategory, v []string, opts ...option) ExchangeScope {
|
|
os := []option{}
|
|
if cat == ExchangeContactFolder || cat == ExchangeEventCalendar || cat == ExchangeMailFolder {
|
|
os = append(os, pathComparator())
|
|
}
|
|
|
|
return set(s, cat, v, append(os, opts...)...)
|
|
}
|
|
|
|
// setDefaults ensures that contact folder, mail folder, and user category
|
|
// scopes all express `AnyTgt` for their child category types.
|
|
func (s ExchangeScope) setDefaults() {
|
|
switch s.Category() {
|
|
case ExchangeContactFolder:
|
|
s[ExchangeContact.String()] = passAny
|
|
|
|
case ExchangeEventCalendar:
|
|
s[ExchangeEvent.String()] = passAny
|
|
|
|
case ExchangeMailFolder:
|
|
s[ExchangeMail.String()] = passAny
|
|
|
|
case ExchangeUser:
|
|
s[ExchangeContactFolder.String()] = passAny
|
|
s[ExchangeContact.String()] = passAny
|
|
s[ExchangeEvent.String()] = passAny
|
|
s[ExchangeMailFolder.String()] = passAny
|
|
s[ExchangeMail.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 exchange) Reduce(
|
|
ctx context.Context,
|
|
deets *details.Details,
|
|
errs *fault.Bus,
|
|
) *details.Details {
|
|
return reduce[ExchangeScope](
|
|
ctx,
|
|
deets,
|
|
s.Selector,
|
|
map[path.CategoryType]exchangeCategory{
|
|
path.ContactsCategory: ExchangeContact,
|
|
path.EventsCategory: ExchangeEvent,
|
|
path.EmailCategory: ExchangeMail,
|
|
},
|
|
errs)
|
|
}
|
|
|
|
// matchesInfo handles the standard behavior when comparing a scope and an ExchangeInfo
|
|
// returns true if the scope and info match for the provided category.
|
|
func (s ExchangeScope) matchesInfo(dii details.ItemInfo) bool {
|
|
info := dii.Exchange
|
|
if info == nil {
|
|
return false
|
|
}
|
|
|
|
infoCat := s.InfoCategory()
|
|
|
|
cfpc := categoryFromItemType(info.ItemType)
|
|
if !typeAndCategoryMatches(infoCat, cfpc) {
|
|
return false
|
|
}
|
|
|
|
i := ""
|
|
|
|
switch infoCat {
|
|
case ExchangeInfoContactName:
|
|
i = info.ContactName
|
|
case ExchangeInfoEventOrganizer:
|
|
i = info.Organizer
|
|
case ExchangeInfoEventRecurs:
|
|
i = strconv.FormatBool(info.EventRecurs)
|
|
case ExchangeInfoEventStartsAfter, ExchangeInfoEventStartsBefore:
|
|
i = dttm.Format(info.EventStart)
|
|
case ExchangeInfoEventSubject:
|
|
i = info.Subject
|
|
case ExchangeInfoMailSender:
|
|
i = info.Sender
|
|
case ExchangeInfoMailSubject:
|
|
i = info.Subject
|
|
case ExchangeInfoMailReceivedAfter, ExchangeInfoMailReceivedBefore:
|
|
i = dttm.Format(info.Received)
|
|
}
|
|
|
|
return s.Matches(infoCat, i)
|
|
}
|
|
|
|
// categoryFromItemType interprets the category represented by the ExchangeInfo
|
|
// struct. Since every ExchangeInfo can hold all exchange data info, the exact
|
|
// type that the struct represents must be compared using its ItemType prop.
|
|
func categoryFromItemType(pct details.ItemType) exchangeCategory {
|
|
switch pct {
|
|
case details.ExchangeContact:
|
|
return ExchangeContact
|
|
case details.ExchangeMail:
|
|
return ExchangeMail
|
|
case details.ExchangeEvent:
|
|
return ExchangeEvent
|
|
}
|
|
|
|
return ExchangeCategoryUnknown
|
|
}
|