Adds collision policy handling to onedrive item posts. This allows us to override the default "replace" behavior that currently returns a 409 for the creation endpoint, in case we want to use skip (ie: fail) or copy handling. --- #### Does this PR need a docs update or release note? - [x] ⛔ No #### Type of change - [x] 🌻 Feature #### Issue(s) * #3562 #### Test Plan - [x] 💪 Manual - [x] ⚡ Unit test - [x] 💚 E2E
288 lines
7.3 KiB
Go
288 lines
7.3 KiB
Go
package sharepoint
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/microsoftgraph/msgraph-sdk-go/drives"
|
|
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
|
|
|
"github.com/alcionai/corso/src/internal/common/ptr"
|
|
"github.com/alcionai/corso/src/internal/m365/onedrive"
|
|
odConsts "github.com/alcionai/corso/src/internal/m365/onedrive/consts"
|
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
|
"github.com/alcionai/corso/src/pkg/control"
|
|
"github.com/alcionai/corso/src/pkg/path"
|
|
"github.com/alcionai/corso/src/pkg/selectors"
|
|
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
|
)
|
|
|
|
var _ onedrive.BackupHandler = &libraryBackupHandler{}
|
|
|
|
type libraryBackupHandler struct {
|
|
ac api.Drives
|
|
scope selectors.SharePointScope
|
|
}
|
|
|
|
func (h libraryBackupHandler) Get(
|
|
ctx context.Context,
|
|
url string,
|
|
headers map[string]string,
|
|
) (*http.Response, error) {
|
|
return h.ac.Get(ctx, url, headers)
|
|
}
|
|
|
|
func (h libraryBackupHandler) PathPrefix(
|
|
tenantID, resourceOwner, driveID string,
|
|
) (path.Path, error) {
|
|
return path.Build(
|
|
tenantID,
|
|
resourceOwner,
|
|
path.SharePointService,
|
|
path.LibrariesCategory,
|
|
false,
|
|
odConsts.DrivesPathDir,
|
|
driveID,
|
|
odConsts.RootPathDir)
|
|
}
|
|
|
|
func (h libraryBackupHandler) CanonicalPath(
|
|
folders *path.Builder,
|
|
tenantID, resourceOwner string,
|
|
) (path.Path, error) {
|
|
return folders.ToDataLayerSharePointPath(tenantID, resourceOwner, path.LibrariesCategory, false)
|
|
}
|
|
|
|
func (h libraryBackupHandler) ServiceCat() (path.ServiceType, path.CategoryType) {
|
|
return path.SharePointService, path.LibrariesCategory
|
|
}
|
|
|
|
func (h libraryBackupHandler) NewDrivePager(
|
|
resourceOwner string,
|
|
fields []string,
|
|
) api.DrivePager {
|
|
return h.ac.NewSiteDrivePager(resourceOwner, fields)
|
|
}
|
|
|
|
func (h libraryBackupHandler) NewItemPager(
|
|
driveID, link string,
|
|
fields []string,
|
|
) api.DriveItemEnumerator {
|
|
return h.ac.NewItemPager(driveID, link, fields)
|
|
}
|
|
|
|
func (h libraryBackupHandler) AugmentItemInfo(
|
|
dii details.ItemInfo,
|
|
item models.DriveItemable,
|
|
size int64,
|
|
parentPath *path.Builder,
|
|
) details.ItemInfo {
|
|
return augmentItemInfo(dii, item, size, parentPath)
|
|
}
|
|
|
|
// constructWebURL is a helper function for recreating the webURL
|
|
// for the originating SharePoint site. Uses the additionalData map
|
|
// from a models.DriveItemable that possesses a downloadURL within the map.
|
|
// Returns "" if the map is nil or key is not present.
|
|
func constructWebURL(adtl map[string]any) string {
|
|
var (
|
|
desiredKey = "@microsoft.graph.downloadUrl"
|
|
sep = `/_layouts`
|
|
url string
|
|
)
|
|
|
|
if adtl == nil {
|
|
return url
|
|
}
|
|
|
|
r := adtl[desiredKey]
|
|
point, ok := r.(*string)
|
|
|
|
if !ok {
|
|
return url
|
|
}
|
|
|
|
value := ptr.Val(point)
|
|
if len(value) == 0 {
|
|
return url
|
|
}
|
|
|
|
temp := strings.Split(value, sep)
|
|
url = temp[0]
|
|
|
|
return url
|
|
}
|
|
|
|
func (h libraryBackupHandler) FormatDisplayPath(
|
|
driveName string,
|
|
pb *path.Builder,
|
|
) string {
|
|
return "/" + driveName + "/" + pb.String()
|
|
}
|
|
|
|
func (h libraryBackupHandler) NewLocationIDer(
|
|
driveID string,
|
|
elems ...string,
|
|
) details.LocationIDer {
|
|
return details.NewSharePointLocationIDer(driveID, elems...)
|
|
}
|
|
|
|
func (h libraryBackupHandler) GetItemPermission(
|
|
ctx context.Context,
|
|
driveID, itemID string,
|
|
) (models.PermissionCollectionResponseable, error) {
|
|
return h.ac.GetItemPermission(ctx, driveID, itemID)
|
|
}
|
|
|
|
func (h libraryBackupHandler) GetItem(
|
|
ctx context.Context,
|
|
driveID, itemID string,
|
|
) (models.DriveItemable, error) {
|
|
return h.ac.GetItem(ctx, driveID, itemID)
|
|
}
|
|
|
|
func (h libraryBackupHandler) IsAllPass() bool {
|
|
return h.scope.IsAny(selectors.SharePointLibraryFolder)
|
|
}
|
|
|
|
func (h libraryBackupHandler) IncludesDir(dir string) bool {
|
|
return h.scope.Matches(selectors.SharePointLibraryFolder, dir)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Restore
|
|
// ---------------------------------------------------------------------------
|
|
|
|
var _ onedrive.RestoreHandler = &libraryRestoreHandler{}
|
|
|
|
type libraryRestoreHandler struct {
|
|
ac api.Drives
|
|
}
|
|
|
|
func NewRestoreHandler(ac api.Client) *libraryRestoreHandler {
|
|
return &libraryRestoreHandler{ac.Drives()}
|
|
}
|
|
|
|
func (h libraryRestoreHandler) AugmentItemInfo(
|
|
dii details.ItemInfo,
|
|
item models.DriveItemable,
|
|
size int64,
|
|
parentPath *path.Builder,
|
|
) details.ItemInfo {
|
|
return augmentItemInfo(dii, item, size, parentPath)
|
|
}
|
|
|
|
func (h libraryRestoreHandler) NewItemContentUpload(
|
|
ctx context.Context,
|
|
driveID, itemID string,
|
|
) (models.UploadSessionable, error) {
|
|
return h.ac.NewItemContentUpload(ctx, driveID, itemID)
|
|
}
|
|
|
|
func (h libraryRestoreHandler) DeleteItemPermission(
|
|
ctx context.Context,
|
|
driveID, itemID, permissionID string,
|
|
) error {
|
|
return h.ac.DeleteItemPermission(ctx, driveID, itemID, permissionID)
|
|
}
|
|
|
|
func (h libraryRestoreHandler) PostItemPermissionUpdate(
|
|
ctx context.Context,
|
|
driveID, itemID string,
|
|
body *drives.ItemItemsItemInvitePostRequestBody,
|
|
) (drives.ItemItemsItemInviteResponseable, error) {
|
|
return h.ac.PostItemPermissionUpdate(ctx, driveID, itemID, body)
|
|
}
|
|
|
|
func (h libraryRestoreHandler) PostItemInContainer(
|
|
ctx context.Context,
|
|
driveID, parentFolderID string,
|
|
newItem models.DriveItemable,
|
|
onCollision control.CollisionPolicy,
|
|
) (models.DriveItemable, error) {
|
|
return h.ac.PostItemInContainer(ctx, driveID, parentFolderID, newItem, onCollision)
|
|
}
|
|
|
|
func (h libraryRestoreHandler) GetFolderByName(
|
|
ctx context.Context,
|
|
driveID, parentFolderID, folderName string,
|
|
) (models.DriveItemable, error) {
|
|
return h.ac.GetFolderByName(ctx, driveID, parentFolderID, folderName)
|
|
}
|
|
|
|
func (h libraryRestoreHandler) GetRootFolder(
|
|
ctx context.Context,
|
|
driveID string,
|
|
) (models.DriveItemable, error) {
|
|
return h.ac.GetRootFolder(ctx, driveID)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Common
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func augmentItemInfo(
|
|
dii details.ItemInfo,
|
|
item models.DriveItemable,
|
|
size int64,
|
|
parentPath *path.Builder,
|
|
) details.ItemInfo {
|
|
var driveName, siteID, driveID, weburl, creatorEmail string
|
|
|
|
// TODO: we rely on this info for details/restore lookups,
|
|
// so if it's nil we have an issue, and will need an alternative
|
|
// way to source the data.
|
|
|
|
if item.GetCreatedBy() != nil && item.GetCreatedBy().GetUser() != nil {
|
|
// User is sometimes not available when created via some
|
|
// external applications (like backup/restore solutions)
|
|
additionalData := item.GetCreatedBy().GetUser().GetAdditionalData()
|
|
|
|
ed, ok := additionalData["email"]
|
|
if !ok {
|
|
ed = additionalData["displayName"]
|
|
}
|
|
|
|
if ed != nil {
|
|
creatorEmail = *ed.(*string)
|
|
}
|
|
}
|
|
|
|
gsi := item.GetSharepointIds()
|
|
if gsi != nil {
|
|
siteID = ptr.Val(gsi.GetSiteId())
|
|
weburl = ptr.Val(gsi.GetSiteUrl())
|
|
|
|
if len(weburl) == 0 {
|
|
weburl = constructWebURL(item.GetAdditionalData())
|
|
}
|
|
}
|
|
|
|
if item.GetParentReference() != nil {
|
|
driveID = ptr.Val(item.GetParentReference().GetDriveId())
|
|
driveName = strings.TrimSpace(ptr.Val(item.GetParentReference().GetName()))
|
|
}
|
|
|
|
var pps string
|
|
if parentPath != nil {
|
|
pps = parentPath.String()
|
|
}
|
|
|
|
dii.SharePoint = &details.SharePointInfo{
|
|
Created: ptr.Val(item.GetCreatedDateTime()),
|
|
DriveID: driveID,
|
|
DriveName: driveName,
|
|
ItemName: ptr.Val(item.GetName()),
|
|
ItemType: details.SharePointLibrary,
|
|
Modified: ptr.Val(item.GetLastModifiedDateTime()),
|
|
Owner: creatorEmail,
|
|
ParentPath: pps,
|
|
SiteID: siteID,
|
|
Size: size,
|
|
WebURL: weburl,
|
|
}
|
|
|
|
return dii
|
|
}
|