Abin Simon 68256cf59f
Change printing of backup stats to single line (#4526)
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
2023-11-16 12:43:27 +00:00

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...)
}