Use selectors in OneDrive CLI (#996)
## Description Adds the following selectors to OneDrive details/restore : - `file-name`, `folder`, `file-created-after`, `file-created-before`, `file-modified-after`, `file-modified-before` Also includes a change where we remove the `drive/<driveID>/root:` prefix from parent path entries in details. This is to improve readability. We will add drive back as a separate item in details if needed later. ## Type of change <!--- Please check the type of change your PR introduces: ---> - [x] 🌻 Feature - [ ] 🐛 Bugfix - [ ] 🗺️ Documentation - [ ] 🤖 Test - [ ] 💻 CI/Deployment - [ ] 🐹 Trivial/Minor ## Issue(s) * #627 ## Test Plan <!-- How will this be tested prior to merging.--> - [ ] 💪 Manual - [x] ⚡ Unit test - [ ] 💚 E2E
This commit is contained in:
parent
88af7f9b7c
commit
03bb63f52d
@ -1,6 +1,8 @@
|
|||||||
package backup
|
package backup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
@ -11,6 +13,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/cli/utils"
|
"github.com/alcionai/corso/src/cli/utils"
|
||||||
"github.com/alcionai/corso/src/internal/model"
|
"github.com/alcionai/corso/src/internal/model"
|
||||||
"github.com/alcionai/corso/src/pkg/backup"
|
"github.com/alcionai/corso/src/pkg/backup"
|
||||||
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
"github.com/alcionai/corso/src/pkg/repository"
|
"github.com/alcionai/corso/src/pkg/repository"
|
||||||
"github.com/alcionai/corso/src/pkg/selectors"
|
"github.com/alcionai/corso/src/pkg/selectors"
|
||||||
)
|
)
|
||||||
@ -21,6 +24,16 @@ import (
|
|||||||
|
|
||||||
const oneDriveServiceCommand = "onedrive"
|
const oneDriveServiceCommand = "onedrive"
|
||||||
|
|
||||||
|
var (
|
||||||
|
folderPaths []string
|
||||||
|
fileNames []string
|
||||||
|
|
||||||
|
fileCreatedAfter string
|
||||||
|
fileCreatedBefore string
|
||||||
|
fileModifiedAfter string
|
||||||
|
fileModifiedBefore string
|
||||||
|
)
|
||||||
|
|
||||||
// called by backup.go to map parent subcommands to provider-specific handling.
|
// called by backup.go to map parent subcommands to provider-specific handling.
|
||||||
func addOneDriveCommands(parent *cobra.Command) *cobra.Command {
|
func addOneDriveCommands(parent *cobra.Command) *cobra.Command {
|
||||||
var (
|
var (
|
||||||
@ -44,6 +57,38 @@ func addOneDriveCommands(parent *cobra.Command) *cobra.Command {
|
|||||||
fs.StringVar(&backupID, "backup", "", "ID of the backup containing the details to be shown")
|
fs.StringVar(&backupID, "backup", "", "ID of the backup containing the details to be shown")
|
||||||
cobra.CheckErr(c.MarkFlagRequired("backup"))
|
cobra.CheckErr(c.MarkFlagRequired("backup"))
|
||||||
|
|
||||||
|
// onedrive hierarchy flags
|
||||||
|
|
||||||
|
fs.StringSliceVar(
|
||||||
|
&folderPaths,
|
||||||
|
"folder", nil,
|
||||||
|
"Select backup details by OneDrive folder; defaults to root")
|
||||||
|
|
||||||
|
fs.StringSliceVar(
|
||||||
|
&fileNames,
|
||||||
|
"file-name", nil,
|
||||||
|
"Select backup details by OneDrive file name")
|
||||||
|
|
||||||
|
// onedrive info flags
|
||||||
|
|
||||||
|
fs.StringVar(
|
||||||
|
&fileCreatedAfter,
|
||||||
|
"file-created-after", "",
|
||||||
|
"Select files created after this datetime")
|
||||||
|
fs.StringVar(
|
||||||
|
&fileCreatedBefore,
|
||||||
|
"file-created-before", "",
|
||||||
|
"Select files created before this datetime")
|
||||||
|
|
||||||
|
fs.StringVar(
|
||||||
|
&fileModifiedAfter,
|
||||||
|
"file-modified-after", "",
|
||||||
|
"Select files modified after this datetime")
|
||||||
|
fs.StringVar(
|
||||||
|
&fileModifiedBefore,
|
||||||
|
"file-modified-before", "",
|
||||||
|
"Select files modified before this datetime")
|
||||||
|
|
||||||
case deleteCommand:
|
case deleteCommand:
|
||||||
c, fs = utils.AddCommand(parent, oneDriveDeleteCmd())
|
c, fs = utils.AddCommand(parent, oneDriveDeleteCmd())
|
||||||
fs.StringVar(&backupID, "backup", "", "ID of the backup containing the details to be shown")
|
fs.StringVar(&backupID, "backup", "", "ID of the backup containing the details to be shown")
|
||||||
@ -202,18 +247,56 @@ func detailsOneDriveCmd(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
defer utils.CloseRepo(ctx, r)
|
defer utils.CloseRepo(ctx, r)
|
||||||
|
|
||||||
ds, _, err := r.BackupDetails(ctx, backupID)
|
opts := utils.OneDriveOpts{
|
||||||
if err != nil {
|
Users: user,
|
||||||
return Only(ctx, errors.Wrap(err, "Failed to get backup details in the repository"))
|
Paths: folderPaths,
|
||||||
|
Names: fileNames,
|
||||||
|
CreatedAfter: fileCreatedAfter,
|
||||||
|
CreatedBefore: fileCreatedBefore,
|
||||||
|
ModifiedAfter: fileModifiedAfter,
|
||||||
|
ModifiedBefore: fileModifiedBefore,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Support selectors and filters
|
ds, err := runDetailsOneDriveCmd(ctx, r, backupID, opts)
|
||||||
|
if err != nil {
|
||||||
|
return Only(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ds.Entries) == 0 {
|
||||||
|
Info(ctx, selectors.ErrorNoMatchingItems)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
ds.PrintEntries(ctx)
|
ds.PrintEntries(ctx)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// runDetailsOneDriveCmd actually performs the lookup in backup details. Assumes
|
||||||
|
// len(backupID) > 0.
|
||||||
|
func runDetailsOneDriveCmd(
|
||||||
|
ctx context.Context,
|
||||||
|
r repository.BackupGetter,
|
||||||
|
backupID string,
|
||||||
|
opts utils.OneDriveOpts,
|
||||||
|
) (*details.Details, error) {
|
||||||
|
d, _, err := r.BackupDetails(ctx, backupID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "Failed to get backup details in the repository")
|
||||||
|
}
|
||||||
|
|
||||||
|
sel := selectors.NewOneDriveRestore()
|
||||||
|
utils.IncludeOneDriveRestoreDataSelectors(sel, opts)
|
||||||
|
utils.FilterOneDriveRestoreInfoSelectors(sel, opts)
|
||||||
|
|
||||||
|
// if no selector flags were specified, get all data in the service.
|
||||||
|
if len(sel.Scopes()) == 0 {
|
||||||
|
sel.Include(sel.Users(selectors.Any()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return sel.Reduce(ctx, d), nil
|
||||||
|
}
|
||||||
|
|
||||||
// `corso backup delete onedrive [<flag>...]`
|
// `corso backup delete onedrive [<flag>...]`
|
||||||
func oneDriveDeleteCmd() *cobra.Command {
|
func oneDriveDeleteCmd() *cobra.Command {
|
||||||
return &cobra.Command{
|
return &cobra.Command{
|
||||||
|
|||||||
@ -15,6 +15,16 @@ import (
|
|||||||
"github.com/alcionai/corso/src/pkg/selectors"
|
"github.com/alcionai/corso/src/pkg/selectors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
folderPaths []string
|
||||||
|
fileNames []string
|
||||||
|
|
||||||
|
fileCreatedAfter string
|
||||||
|
fileCreatedBefore string
|
||||||
|
fileModifiedAfter string
|
||||||
|
fileModifiedBefore string
|
||||||
|
)
|
||||||
|
|
||||||
// called by restore.go to map parent subcommands to provider-specific handling.
|
// called by restore.go to map parent subcommands to provider-specific handling.
|
||||||
func addOneDriveCommands(parent *cobra.Command) *cobra.Command {
|
func addOneDriveCommands(parent *cobra.Command) *cobra.Command {
|
||||||
var (
|
var (
|
||||||
@ -37,6 +47,38 @@ func addOneDriveCommands(parent *cobra.Command) *cobra.Command {
|
|||||||
"user", nil,
|
"user", nil,
|
||||||
"Restore all data by user ID; accepts "+utils.Wildcard+" to select all users")
|
"Restore all data by user ID; accepts "+utils.Wildcard+" to select all users")
|
||||||
|
|
||||||
|
// onedrive hierarchy (path/name) flags
|
||||||
|
|
||||||
|
fs.StringSliceVar(
|
||||||
|
&folderPaths,
|
||||||
|
"folder", nil,
|
||||||
|
"Restore items by OneDrive folder; defaults to root")
|
||||||
|
|
||||||
|
fs.StringSliceVar(
|
||||||
|
&fileNames,
|
||||||
|
"file-name", nil,
|
||||||
|
"Restore items by OneDrive file name")
|
||||||
|
|
||||||
|
// onedrive info flags
|
||||||
|
|
||||||
|
fs.StringVar(
|
||||||
|
&fileCreatedAfter,
|
||||||
|
"file-created-after", "",
|
||||||
|
"Restore files created after this datetime")
|
||||||
|
fs.StringVar(
|
||||||
|
&fileCreatedBefore,
|
||||||
|
"file-created-before", "",
|
||||||
|
"Restore files created before this datetime")
|
||||||
|
|
||||||
|
fs.StringVar(
|
||||||
|
&fileModifiedAfter,
|
||||||
|
"file-modified-after", "",
|
||||||
|
"Restore files modified after this datetime")
|
||||||
|
fs.StringVar(
|
||||||
|
&fileModifiedBefore,
|
||||||
|
"file-modified-before", "",
|
||||||
|
"Restore files modified before this datetime")
|
||||||
|
|
||||||
// others
|
// others
|
||||||
options.AddOperationFlags(c)
|
options.AddOperationFlags(c)
|
||||||
}
|
}
|
||||||
@ -80,11 +122,20 @@ func restoreOneDriveCmd(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
defer utils.CloseRepo(ctx, r)
|
defer utils.CloseRepo(ctx, r)
|
||||||
|
|
||||||
sel := selectors.NewOneDriveRestore()
|
opts := utils.OneDriveOpts{
|
||||||
if user != nil {
|
Users: user,
|
||||||
sel.Include(sel.Users(user))
|
Paths: folderPaths,
|
||||||
|
Names: fileNames,
|
||||||
|
CreatedAfter: fileCreatedAfter,
|
||||||
|
CreatedBefore: fileCreatedBefore,
|
||||||
|
ModifiedAfter: fileModifiedAfter,
|
||||||
|
ModifiedBefore: fileModifiedBefore,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sel := selectors.NewOneDriveRestore()
|
||||||
|
utils.IncludeOneDriveRestoreDataSelectors(sel, opts)
|
||||||
|
utils.FilterOneDriveRestoreInfoSelectors(sel, opts)
|
||||||
|
|
||||||
// if no selector flags were specified, get all data in the service.
|
// if no selector flags were specified, get all data in the service.
|
||||||
if len(sel.Scopes()) == 0 {
|
if len(sel.Scopes()) == 0 {
|
||||||
sel.Include(sel.Users(selectors.Any()))
|
sel.Include(sel.Users(selectors.Any()))
|
||||||
|
|||||||
@ -2,8 +2,20 @@ package utils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/src/pkg/selectors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type OneDriveOpts struct {
|
||||||
|
Users []string
|
||||||
|
Names []string
|
||||||
|
Paths []string
|
||||||
|
CreatedAfter string
|
||||||
|
CreatedBefore string
|
||||||
|
ModifiedAfter string
|
||||||
|
ModifiedBefore string
|
||||||
|
}
|
||||||
|
|
||||||
// ValidateOneDriveRestoreFlags checks common flags for correctness and interdependencies
|
// ValidateOneDriveRestoreFlags checks common flags for correctness and interdependencies
|
||||||
func ValidateOneDriveRestoreFlags(backupID string) error {
|
func ValidateOneDriveRestoreFlags(backupID string) error {
|
||||||
if len(backupID) == 0 {
|
if len(backupID) == 0 {
|
||||||
@ -12,3 +24,58 @@ func ValidateOneDriveRestoreFlags(backupID string) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddOneDriveFilter adds the scope of the provided values to the selector's
|
||||||
|
// filter set
|
||||||
|
func AddOneDriveFilter(
|
||||||
|
sel *selectors.OneDriveRestore,
|
||||||
|
v string,
|
||||||
|
f func(string) []selectors.OneDriveScope,
|
||||||
|
) {
|
||||||
|
if len(v) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sel.Filter(f(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IncludeOneDriveRestoreDataSelectors builds the common data-selector
|
||||||
|
// inclusions for OneDrive commands.
|
||||||
|
func IncludeOneDriveRestoreDataSelectors(
|
||||||
|
sel *selectors.OneDriveRestore,
|
||||||
|
opts OneDriveOpts,
|
||||||
|
) {
|
||||||
|
if len(opts.Users) == 0 {
|
||||||
|
opts.Users = selectors.Any()
|
||||||
|
}
|
||||||
|
|
||||||
|
lp, ln := len(opts.Paths), len(opts.Names)
|
||||||
|
|
||||||
|
// either scope the request to a set of users
|
||||||
|
if lp+ln == 0 {
|
||||||
|
sel.Include(sel.Users(opts.Users))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if lp == 0 {
|
||||||
|
opts.Paths = selectors.Any()
|
||||||
|
}
|
||||||
|
|
||||||
|
if ln == 0 {
|
||||||
|
opts.Names = selectors.Any()
|
||||||
|
}
|
||||||
|
|
||||||
|
sel.Include(sel.Items(opts.Users, opts.Paths, opts.Names))
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilterOneDriveRestoreInfoSelectors builds the common info-selector filters.
|
||||||
|
func FilterOneDriveRestoreInfoSelectors(
|
||||||
|
sel *selectors.OneDriveRestore,
|
||||||
|
opts OneDriveOpts,
|
||||||
|
) {
|
||||||
|
AddOneDriveFilter(sel, opts.CreatedAfter, sel.CreatedAfter)
|
||||||
|
AddOneDriveFilter(sel, opts.CreatedBefore, sel.CreatedBefore)
|
||||||
|
AddOneDriveFilter(sel, opts.ModifiedAfter, sel.ModifiedAfter)
|
||||||
|
AddOneDriveFilter(sel, opts.ModifiedBefore, sel.ModifiedBefore)
|
||||||
|
}
|
||||||
|
|||||||
@ -111,6 +111,14 @@ func (oc *Collection) populateItems(ctx context.Context) {
|
|||||||
itemsRead = 0
|
itemsRead = 0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Retrieve the OneDrive folder path to set later in
|
||||||
|
// `details.OneDriveInfo`
|
||||||
|
parentPathString, err := getDriveFolderPath(oc.folderPath)
|
||||||
|
if err != nil {
|
||||||
|
oc.reportAsCompleted(ctx, 0, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
for _, itemID := range oc.driveItemIDs {
|
for _, itemID := range oc.driveItemIDs {
|
||||||
// Read the item
|
// Read the item
|
||||||
itemInfo, itemData, err := oc.itemReader(ctx, oc.service, oc.driveID, itemID)
|
itemInfo, itemData, err := oc.itemReader(ctx, oc.service, oc.driveID, itemID)
|
||||||
@ -126,7 +134,7 @@ func (oc *Collection) populateItems(ctx context.Context) {
|
|||||||
// Item read successfully, add to collection
|
// Item read successfully, add to collection
|
||||||
itemsRead++
|
itemsRead++
|
||||||
|
|
||||||
itemInfo.ParentPath = oc.folderPath.String()
|
itemInfo.ParentPath = parentPathString
|
||||||
|
|
||||||
oc.data <- &Item{
|
oc.data <- &Item{
|
||||||
id: itemInfo.ItemName,
|
id: itemInfo.ItemName,
|
||||||
@ -135,6 +143,10 @@ func (oc *Collection) populateItems(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
oc.reportAsCompleted(ctx, itemsRead, errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (oc *Collection) reportAsCompleted(ctx context.Context, itemsRead int, errs error) {
|
||||||
close(oc.data)
|
close(oc.data)
|
||||||
|
|
||||||
status := support.CreateStatus(ctx, support.Backup,
|
status := support.CreateStatus(ctx, support.Backup,
|
||||||
|
|||||||
@ -60,7 +60,9 @@ func (suite *OneDriveCollectionSuite) TestOneDriveCollection() {
|
|||||||
wg := sync.WaitGroup{}
|
wg := sync.WaitGroup{}
|
||||||
collStatus := support.ConnectorOperationStatus{}
|
collStatus := support.ConnectorOperationStatus{}
|
||||||
|
|
||||||
folderPath, err := getCanonicalPath("dir1/dir2/dir3", "a-tenant", "a-user")
|
folderPath, err := getCanonicalPath("drive/driveID1/root:/dir1/dir2/dir3", "a-tenant", "a-user")
|
||||||
|
require.NoError(t, err)
|
||||||
|
driveFolderPath, err := getDriveFolderPath(folderPath)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
coll := NewCollection(folderPath, "fakeDriveID", suite, suite.testStatusUpdater(&wg, &collStatus))
|
coll := NewCollection(folderPath, "fakeDriveID", suite, suite.testStatusUpdater(&wg, &collStatus))
|
||||||
@ -106,7 +108,7 @@ func (suite *OneDriveCollectionSuite) TestOneDriveCollection() {
|
|||||||
require.NotNil(t, readItemInfo.Info())
|
require.NotNil(t, readItemInfo.Info())
|
||||||
require.NotNil(t, readItemInfo.Info().OneDrive)
|
require.NotNil(t, readItemInfo.Info().OneDrive)
|
||||||
assert.Equal(t, testItemName, readItemInfo.Info().OneDrive.ItemName)
|
assert.Equal(t, testItemName, readItemInfo.Info().OneDrive.ItemName)
|
||||||
assert.Equal(t, folderPath.String(), readItemInfo.Info().OneDrive.ParentPath)
|
assert.Equal(t, driveFolderPath, readItemInfo.Info().OneDrive.ParentPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *OneDriveCollectionSuite) TestOneDriveCollectionReadError() {
|
func (suite *OneDriveCollectionSuite) TestOneDriveCollectionReadError() {
|
||||||
|
|||||||
@ -82,6 +82,16 @@ func getCanonicalPath(p, tenant, user string) (path.Path, error) {
|
|||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the path to the folder within the drive (i.e. under `root:`)
|
||||||
|
func getDriveFolderPath(p path.Path) (string, error) {
|
||||||
|
drivePath, err := toOneDrivePath(p)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return path.Builder{}.Append(drivePath.folders...).String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
// updateCollections initializes and adds the provided OneDrive items to Collections
|
// updateCollections initializes and adds the provided OneDrive items to Collections
|
||||||
// A new collection is created for every OneDrive folder (or package)
|
// A new collection is created for every OneDrive folder (or package)
|
||||||
func (c *Collections) updateCollections(ctx context.Context, driveID string, items []models.DriveItemable) error {
|
func (c *Collections) updateCollections(ctx context.Context, driveID string, items []models.DriveItemable) error {
|
||||||
|
|||||||
@ -149,12 +149,24 @@ func (op *RestoreOperation) Run(ctx context.Context) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case selectors.ServiceOneDrive:
|
case selectors.ServiceOneDrive:
|
||||||
// TODO: Reduce `details` here when we add support for OneDrive restore filters
|
odr, err := op.Selectors.ToOneDriveRestore()
|
||||||
fds = d
|
if err != nil {
|
||||||
|
opStats.readErr = err
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// format the details and retrieve the items from kopia
|
||||||
|
fds = odr.Reduce(ctx, d)
|
||||||
|
if len(fds.Entries) == 0 {
|
||||||
|
return selectors.ErrorNoMatchingItems
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return errors.Errorf("Service %s not supported", op.Selectors.Service)
|
return errors.Errorf("Service %s not supported", op.Selectors.Service)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.Ctx(ctx).Infof("Discovered %d items in backup %s to restore", len(fds.Entries), op.BackupID)
|
||||||
|
|
||||||
fdsPaths := fds.Paths()
|
fdsPaths := fds.Paths()
|
||||||
paths := make([]path.Path, len(fdsPaths))
|
paths := make([]path.Path, len(fdsPaths))
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,9 @@ package selectors
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/src/internal/common"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
|
"github.com/alcionai/corso/src/pkg/filters"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -202,6 +204,65 @@ func (s *oneDrive) Items(users, folders, items []string) []OneDriveScope {
|
|||||||
return scopes
|
return scopes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------
|
||||||
|
// Filter Factories
|
||||||
|
|
||||||
|
// CreatedAfter produces a OneDrive item created-after filter scope.
|
||||||
|
// Matches any item where the created 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 *oneDrive) CreatedAfter(timeStrings string) []OneDriveScope {
|
||||||
|
return []OneDriveScope{
|
||||||
|
makeFilterScope[OneDriveScope](
|
||||||
|
OneDriveItem,
|
||||||
|
FileFilterCreatedAfter,
|
||||||
|
[]string{timeStrings},
|
||||||
|
wrapFilter(filters.Less)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedBefore produces a OneDrive item created-before filter scope.
|
||||||
|
// Matches any item where the created 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 (s *oneDrive) CreatedBefore(timeStrings string) []OneDriveScope {
|
||||||
|
return []OneDriveScope{
|
||||||
|
makeFilterScope[OneDriveScope](
|
||||||
|
OneDriveItem,
|
||||||
|
FileFilterCreatedBefore,
|
||||||
|
[]string{timeStrings},
|
||||||
|
wrapFilter(filters.Greater)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModifiedAfter produces a OneDrive item modified-after filter scope.
|
||||||
|
// Matches any item where the modified 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 *oneDrive) ModifiedAfter(timeStrings string) []OneDriveScope {
|
||||||
|
return []OneDriveScope{
|
||||||
|
makeFilterScope[OneDriveScope](
|
||||||
|
OneDriveItem,
|
||||||
|
FileFilterModifiedAfter,
|
||||||
|
[]string{timeStrings},
|
||||||
|
wrapFilter(filters.Less)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModifiedBefore produces a OneDrive item modified-before filter scope.
|
||||||
|
// Matches any item where the modified 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 (s *oneDrive) ModifiedBefore(timeStrings string) []OneDriveScope {
|
||||||
|
return []OneDriveScope{
|
||||||
|
makeFilterScope[OneDriveScope](
|
||||||
|
OneDriveItem,
|
||||||
|
FileFilterModifiedBefore,
|
||||||
|
[]string{timeStrings},
|
||||||
|
wrapFilter(filters.Greater)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Categories
|
// Categories
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@ -219,6 +280,12 @@ const (
|
|||||||
OneDriveUser oneDriveCategory = "OneDriveUser"
|
OneDriveUser oneDriveCategory = "OneDriveUser"
|
||||||
OneDriveItem oneDriveCategory = "OneDriveItem"
|
OneDriveItem oneDriveCategory = "OneDriveItem"
|
||||||
OneDriveFolder oneDriveCategory = "OneDriveFolder"
|
OneDriveFolder oneDriveCategory = "OneDriveFolder"
|
||||||
|
|
||||||
|
// filterable topics identified by OneDrive
|
||||||
|
FileFilterCreatedAfter oneDriveCategory = "FileFilterCreatedAfter"
|
||||||
|
FileFilterCreatedBefore oneDriveCategory = "FileFilterCreatedBefore"
|
||||||
|
FileFilterModifiedAfter oneDriveCategory = "FileFilterModifiedAfter"
|
||||||
|
FileFilterModifiedBefore oneDriveCategory = "FileFilterModifiedBefore"
|
||||||
)
|
)
|
||||||
|
|
||||||
// oneDrivePathSet describes the category type keys used in OneDrive paths.
|
// oneDrivePathSet describes the category type keys used in OneDrive paths.
|
||||||
@ -240,7 +307,9 @@ func (c oneDriveCategory) String() string {
|
|||||||
// Ex: ServiceUser.leafCat() => ServiceUser
|
// Ex: ServiceUser.leafCat() => ServiceUser
|
||||||
func (c oneDriveCategory) leafCat() categorizer {
|
func (c oneDriveCategory) leafCat() categorizer {
|
||||||
switch c {
|
switch c {
|
||||||
case OneDriveFolder, OneDriveItem:
|
case OneDriveFolder, OneDriveItem,
|
||||||
|
FileFilterCreatedAfter, FileFilterCreatedBefore,
|
||||||
|
FileFilterModifiedAfter, FileFilterModifiedBefore:
|
||||||
return OneDriveItem
|
return OneDriveItem
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -269,9 +338,12 @@ func (c oneDriveCategory) isLeaf() bool {
|
|||||||
// [tenantID, service, userPN, category, folder, fileID]
|
// [tenantID, service, userPN, category, folder, fileID]
|
||||||
// => {odUser: userPN, odFolder: folder, odFileID: fileID}
|
// => {odUser: userPN, odFolder: folder, odFileID: fileID}
|
||||||
func (c oneDriveCategory) pathValues(p path.Path) map[categorizer]string {
|
func (c oneDriveCategory) pathValues(p path.Path) map[categorizer]string {
|
||||||
|
// Ignore `drives/<driveID>/root:` for folder comparison
|
||||||
|
folder := path.Builder{}.Append(p.Folders()...).PopFront().PopFront().PopFront().String()
|
||||||
|
|
||||||
return map[categorizer]string{
|
return map[categorizer]string{
|
||||||
OneDriveUser: p.ResourceOwner(),
|
OneDriveUser: p.ResourceOwner(),
|
||||||
OneDriveFolder: p.Folder(), // TODO: Should we filter out the DriveID here?
|
OneDriveFolder: folder,
|
||||||
OneDriveItem: p.Item(),
|
OneDriveItem: p.Item(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -360,31 +432,18 @@ func (s OneDriveScope) matchesInfo(dii details.ItemInfo) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// the scope must define targets to match on
|
|
||||||
filterCat := s.FilterCategory()
|
filterCat := s.FilterCategory()
|
||||||
targets := s.Get(filterCat)
|
|
||||||
|
|
||||||
if len(targets) == 0 {
|
i := ""
|
||||||
return false
|
|
||||||
|
switch filterCat {
|
||||||
|
case FileFilterCreatedAfter, FileFilterCreatedBefore:
|
||||||
|
i = common.FormatTime(info.Created)
|
||||||
|
case FileFilterModifiedAfter, FileFilterModifiedBefore:
|
||||||
|
i = common.FormatTime(info.LastModified)
|
||||||
}
|
}
|
||||||
|
|
||||||
if targets[0] == AnyTgt {
|
return s.Matches(filterCat, i)
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if targets[0] == NoneTgt {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// any of the targets for a given info filter may succeed.
|
|
||||||
for _, target := range targets {
|
|
||||||
switch filterCat {
|
|
||||||
// TODO: populate oneDrive filter checks
|
|
||||||
default:
|
|
||||||
return target != NoneTgt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@ -3,11 +3,13 @@ package selectors
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/src/internal/common"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
)
|
)
|
||||||
@ -181,9 +183,9 @@ func (suite *OneDriveSelectorSuite) TestToOneDriveRestore() {
|
|||||||
|
|
||||||
func (suite *OneDriveSelectorSuite) TestOneDriveRestore_Reduce() {
|
func (suite *OneDriveSelectorSuite) TestOneDriveRestore_Reduce() {
|
||||||
var (
|
var (
|
||||||
file = stubRepoRef(path.OneDriveService, path.FilesCategory, "uid", "folderA/folderB", "file")
|
file = stubRepoRef(path.OneDriveService, path.FilesCategory, "uid", "drive/driveID/root:/folderA/folderB", "file")
|
||||||
file2 = stubRepoRef(path.OneDriveService, path.FilesCategory, "uid", "folderA/folderC", "file2")
|
file2 = stubRepoRef(path.OneDriveService, path.FilesCategory, "uid", "drive/driveID/root:/folderA/folderC", "file2")
|
||||||
file3 = stubRepoRef(path.OneDriveService, path.FilesCategory, "uid", "folderD/folderE", "file3")
|
file3 = stubRepoRef(path.OneDriveService, path.FilesCategory, "uid", "drive/driveID/root:/folderD/folderE", "file3")
|
||||||
)
|
)
|
||||||
|
|
||||||
deets := &details.Details{
|
deets := &details.Details{
|
||||||
@ -267,3 +269,67 @@ func (suite *OneDriveSelectorSuite) TestOneDriveRestore_Reduce() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (suite *OneDriveSelectorSuite) TestOneDriveCategory_PathValues() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
pathBuilder := path.Builder{}.Append("drive", "driveID", "root:", "dir1", "dir2", "file")
|
||||||
|
filePath, err := pathBuilder.ToDataLayerOneDrivePath("tenant", "user", true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
expected := map[categorizer]string{
|
||||||
|
OneDriveUser: "user",
|
||||||
|
OneDriveFolder: "dir1/dir2",
|
||||||
|
OneDriveItem: "file",
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, expected, OneDriveItem.pathValues(filePath))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *OneDriveSelectorSuite) TestOneDriveScope_MatchesInfo() {
|
||||||
|
ods := NewOneDriveRestore()
|
||||||
|
|
||||||
|
var (
|
||||||
|
epoch = time.Time{}
|
||||||
|
now = time.Now()
|
||||||
|
future = now.Add(1 * time.Minute)
|
||||||
|
)
|
||||||
|
|
||||||
|
itemInfo := details.ItemInfo{
|
||||||
|
OneDrive: &details.OneDriveInfo{
|
||||||
|
ItemType: details.OneDriveItem,
|
||||||
|
ParentPath: "folder1/folder2",
|
||||||
|
ItemName: "file1",
|
||||||
|
Size: 10,
|
||||||
|
Created: now,
|
||||||
|
LastModified: now,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
table := []struct {
|
||||||
|
name string
|
||||||
|
scope []OneDriveScope
|
||||||
|
expect assert.BoolAssertionFunc
|
||||||
|
}{
|
||||||
|
{"file create after the epoch", ods.CreatedAfter(common.FormatTime(epoch)), assert.True},
|
||||||
|
{"file create after now", ods.CreatedAfter(common.FormatTime(now)), assert.False},
|
||||||
|
{"file create after later", ods.CreatedAfter(common.FormatTime(future)), assert.False},
|
||||||
|
{"file create before future", ods.CreatedBefore(common.FormatTime(future)), assert.True},
|
||||||
|
{"file create before now", ods.CreatedBefore(common.FormatTime(now)), assert.False},
|
||||||
|
{"file create before epoch", ods.CreatedBefore(common.FormatTime(now)), assert.False},
|
||||||
|
{"file modified after the epoch", ods.ModifiedAfter(common.FormatTime(epoch)), assert.True},
|
||||||
|
{"file modified after now", ods.ModifiedAfter(common.FormatTime(now)), assert.False},
|
||||||
|
{"file modified after later", ods.ModifiedAfter(common.FormatTime(future)), assert.False},
|
||||||
|
{"file modified before future", ods.ModifiedBefore(common.FormatTime(future)), assert.True},
|
||||||
|
{"file modified before now", ods.ModifiedBefore(common.FormatTime(now)), assert.False},
|
||||||
|
{"file modified before epoch", ods.ModifiedBefore(common.FormatTime(now)), assert.False},
|
||||||
|
}
|
||||||
|
for _, test := range table {
|
||||||
|
suite.T().Run(test.name, func(t *testing.T) {
|
||||||
|
scopes := setScopesToDefault(test.scope)
|
||||||
|
for _, scope := range scopes {
|
||||||
|
test.expect(t, scope.matchesInfo(itemInfo))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user