Previously we were printing a table for a single item which, for one
was unnecessary, but this also make the output of a backup more
compact.
Before:
```
Logging to file: /home/meain/.cache/corso/logs/2023-10-20T10-21-23Z.log
Connecting to M365 done
Connecting to repository 1s done
Backing Up ∙ Teams Testing (meain)
Discovering items to backup 27s done
Libraries (TeamsTestingmeain) done (found 0 items)
Libraries (TeamsTestingmeain-Shared0) done (found 0 items)
Libraries (TeamsTestingmeain-Private2) done (found 0 items)
Messages 7s done (found 6 channels)
Backing up data 7s done
Backup complete ∙ 7b40dd40-f808-4d57-8e39-b4553e48dc5d
ID Bytes Uploaded Items Uploaded Items Skipped Errors
7b40dd40-f808-4d57-8e39-b4553e48dc5d 0 B 0 0 0
Completed Backups:
ID Started At Duration Status Resource Owner
7b40dd40-f808-4d57-8e39-b4553e48dc5d 2023-10-20T10:21:32Z 36.632747912s Completed Teams Testing (meain)
```
After:
```
Connecting to M365 done
Connecting to repository 1s done
Backing Up ∙ Teams Testing (meain)
Discovering items to backup 31s done
Libraries (TeamsTestingmeain) done (found 0 items)
Libraries (TeamsTestingmeain-Shared0) done (found 0 items)
Libraries (TeamsTestingmeain-Private2) done (found 0 items)
Messages 9s done (found 6 channels)
Backing up data 7s done
Backup complete ∙ ffb2f619-1cb7-4a11-b3e2-7300aa513c6a
Bytes Uploaded: 0 B | Items Uploaded: 0 | Items Skipped: 0 | Errors: 0
Completed Backups:
ID ID Started At Duration Status Resource Owner
ffb2f619-1cb7-4a11-b3e2-7300aa513c6a 2023-10-20T10:23:35Z 40.096203016s Completed Teams Testing (meain)
```
---
#### Does this PR need a docs update or release note?
- [x] ✅ Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [ ] ⛔ No
#### Type of change
<!--- Please check the type of change your PR introduces: --->
- [x] 🌻 Feature
- [ ] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [ ] 🤖 Supportability/Tests
- [ ] 💻 CI/Deployment
- [ ] 🧹 Tech Debt/Cleanup
#### Issue(s)
<!-- Can reference multiple issues. Use one of the following "magic words" - "closes, fixes" to auto-close the Github issue. -->
* #<issue>
#### Test Plan
<!-- How will this be tested prior to merging.-->
- [ ] 💪 Manual
- [x] ⚡ Unit test
- [ ] 💚 E2E
203 lines
5.7 KiB
Go
203 lines
5.7 KiB
Go
package details
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/alcionai/clues"
|
|
|
|
"github.com/alcionai/corso/src/cli/print"
|
|
"github.com/alcionai/corso/src/internal/version"
|
|
"github.com/alcionai/corso/src/pkg/path"
|
|
)
|
|
|
|
// Add a new type so we can transparently use PrintAll in different situations.
|
|
type entrySet []*Entry
|
|
|
|
func (ents entrySet) PrintEntries(ctx context.Context) {
|
|
printEntries(ctx, ents)
|
|
}
|
|
|
|
// MaybePrintEntries is same as PrintEntries, but only prints if we
|
|
// have less than 15 items or is not json output.
|
|
func (ents entrySet) MaybePrintEntries(ctx context.Context) {
|
|
if len(ents) <= maxPrintLimit ||
|
|
print.DisplayJSONFormat() ||
|
|
print.DisplayVerbose() {
|
|
printEntries(ctx, ents)
|
|
}
|
|
}
|
|
|
|
// Entry describes a single item stored in a Backup
|
|
type Entry struct {
|
|
// RepoRef is the full storage path of the item in Kopia
|
|
RepoRef string `json:"repoRef"`
|
|
ShortRef string `json:"shortRef"`
|
|
ParentRef string `json:"parentRef,omitempty"`
|
|
|
|
// LocationRef contains the logical path structure by its human-readable
|
|
// display names. IE: If an item is located at "/Inbox/Important", we
|
|
// hold that string in the LocationRef, while the actual IDs of each
|
|
// container are used for the RepoRef.
|
|
// LocationRef only holds the container values, and does not include
|
|
// the metadata prefixes (tenant, service, owner, etc) found in the
|
|
// repoRef.
|
|
// Currently only implemented for Exchange Calendars.
|
|
LocationRef string `json:"locationRef,omitempty"`
|
|
|
|
// ItemRef contains the stable id of the item itself. ItemRef is not
|
|
// guaranteed to be unique within a repository. Uniqueness guarantees
|
|
// maximally inherit from the source item. Eg: Entries for m365 mail items
|
|
// are only as unique as m365 mail item IDs themselves.
|
|
ItemRef string `json:"itemRef,omitempty"`
|
|
|
|
ItemInfo
|
|
}
|
|
|
|
// ToLocationIDer takes a backup version and produces the unique location for
|
|
// this entry if possible. Reasons it may not be possible to produce the unique
|
|
// location include an unsupported backup version or missing information.
|
|
//
|
|
// TODO(ashmrtn): Remove this function completely if we ever decide to sunset
|
|
// older corso versions that didn't populate LocationRef.
|
|
func (de Entry) ToLocationIDer(backupVersion int) (LocationIDer, error) {
|
|
if len(de.LocationRef) > 0 {
|
|
baseLoc, err := path.Builder{}.SplitUnescapeAppend(de.LocationRef)
|
|
if err != nil {
|
|
return nil, clues.Wrap(err, "parsing base location info").
|
|
With("location_ref", de.LocationRef)
|
|
}
|
|
|
|
// Individual services may add additional info to the base and return that.
|
|
return de.ItemInfo.uniqueLocation(baseLoc)
|
|
}
|
|
|
|
rr, err := path.FromDataLayerPath(de.RepoRef, true)
|
|
if err != nil {
|
|
return nil, clues.Wrap(err, "getting item RepoRef").
|
|
With("repo_ref", de.RepoRef)
|
|
}
|
|
|
|
var baseLoc *path.Builder
|
|
|
|
switch de.ItemInfo.infoType() {
|
|
case ExchangeEvent:
|
|
if backupVersion >= 2 {
|
|
return nil, clues.New("no previous location for calendar entry").
|
|
With("repo_ref", rr)
|
|
}
|
|
|
|
fallthrough
|
|
case ExchangeMail, ExchangeContact:
|
|
baseLoc = path.Builder{}.Append(rr.Folders()...)
|
|
|
|
case OneDriveItem, SharePointLibrary:
|
|
if backupVersion >= version.OneDrive7LocationRef {
|
|
return nil, clues.New("no previous location for drive entry").
|
|
With("repo_ref", rr)
|
|
}
|
|
|
|
p, err := path.ToDrivePath(rr)
|
|
if err != nil {
|
|
return nil, clues.New("converting RepoRef to drive path").
|
|
With("repo_ref", rr)
|
|
}
|
|
|
|
baseLoc = path.Builder{}.Append(p.Root).Append(p.Folders...)
|
|
}
|
|
|
|
if baseLoc == nil {
|
|
return nil, clues.New("unable to extract LocationRef from RepoRef").
|
|
With("repo_ref", rr)
|
|
}
|
|
|
|
// Individual services may add additional info to the base and return that.
|
|
return de.ItemInfo.uniqueLocation(baseLoc)
|
|
}
|
|
|
|
// Check if a file is a metadata file. These are used to store
|
|
// additional data like permissions (in case of Drive items) and are
|
|
// not to be treated as regular files.
|
|
func (de Entry) isMetaFile() bool {
|
|
// sharepoint types not needed, since sharepoint permissions were
|
|
// added after IsMeta was deprecated.
|
|
// Earlier onedrive backups used to store both metafiles and files in details.
|
|
// So filter out just the onedrive items and check for metafiles
|
|
return de.ItemInfo.OneDrive != nil && de.ItemInfo.OneDrive.IsMeta
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CLI Output
|
|
// --------------------------------------------------------------------------------
|
|
|
|
// interface compliance checks
|
|
var _ print.Printable = &Entry{}
|
|
|
|
// MinimumPrintable DetailsEntries is a passthrough func, because no
|
|
// reduction is needed for the json output.
|
|
func (de Entry) MinimumPrintable() any {
|
|
return de
|
|
}
|
|
|
|
// Headers returns the human-readable names of properties in a DetailsEntry
|
|
// for printing out to a terminal in a columnar display.
|
|
func (de Entry) Headers(skipID bool) []string {
|
|
hs := []string{}
|
|
|
|
if de.ItemInfo.Folder != nil {
|
|
hs = de.ItemInfo.Folder.Headers()
|
|
}
|
|
|
|
if de.ItemInfo.Exchange != nil {
|
|
hs = de.ItemInfo.Exchange.Headers()
|
|
}
|
|
|
|
if de.ItemInfo.SharePoint != nil {
|
|
hs = de.ItemInfo.SharePoint.Headers()
|
|
}
|
|
|
|
if de.ItemInfo.OneDrive != nil {
|
|
hs = de.ItemInfo.OneDrive.Headers()
|
|
}
|
|
|
|
if de.ItemInfo.Groups != nil {
|
|
hs = de.ItemInfo.Groups.Headers()
|
|
}
|
|
|
|
if skipID {
|
|
return hs
|
|
}
|
|
|
|
return append([]string{"ID"}, hs...)
|
|
}
|
|
|
|
// Values returns the values matching the Headers list.
|
|
func (de Entry) Values(skipID bool) []string {
|
|
vs := []string{}
|
|
|
|
if de.ItemInfo.Folder != nil {
|
|
vs = de.ItemInfo.Folder.Values()
|
|
}
|
|
|
|
if de.ItemInfo.Exchange != nil {
|
|
vs = de.ItemInfo.Exchange.Values()
|
|
}
|
|
|
|
if de.ItemInfo.SharePoint != nil {
|
|
vs = de.ItemInfo.SharePoint.Values()
|
|
}
|
|
|
|
if de.ItemInfo.OneDrive != nil {
|
|
vs = de.ItemInfo.OneDrive.Values()
|
|
}
|
|
|
|
if de.ItemInfo.Groups != nil {
|
|
vs = de.ItemInfo.Groups.Values()
|
|
}
|
|
|
|
if skipID {
|
|
return vs
|
|
}
|
|
|
|
return append([]string{de.ShortRef}, vs...)
|
|
}
|