mirror of
https://github.com/kopia/kopia.git
synced 2025-12-23 22:57:50 -05:00
156 lines
3.8 KiB
Go
156 lines
3.8 KiB
Go
package cli
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/kopia/kopia/fs"
|
|
"github.com/kopia/kopia/repo"
|
|
"github.com/kopia/kopia/repo/object"
|
|
"github.com/kopia/kopia/snapshot/snapshotfs"
|
|
)
|
|
|
|
type commandList struct {
|
|
long bool
|
|
humanReadable bool
|
|
recursive bool
|
|
showOID bool
|
|
errorSummary bool
|
|
path string
|
|
|
|
out textOutput
|
|
}
|
|
|
|
func (c *commandList) setup(svc appServices, parent commandParent) {
|
|
cmd := parent.Command("list", "List a directory stored in repository object.").Alias("ls")
|
|
|
|
cmd.Flag("long", "Long output").Short('l').BoolVar(&c.long)
|
|
cmd.Flag("human-readable", "Show human-readable sizes").Short('h').BoolVar(&c.humanReadable)
|
|
cmd.Flag("recursive", "Recursive output").Short('r').BoolVar(&c.recursive)
|
|
cmd.Flag("show-object-id", "Show object IDs").Short('o').BoolVar(&c.showOID)
|
|
cmd.Flag("error-summary", "Emit error summary").Default("true").BoolVar(&c.errorSummary)
|
|
cmd.Arg("object-path", "Path").Required().StringVar(&c.path)
|
|
cmd.Action(svc.repositoryReaderAction(c.run))
|
|
|
|
c.out.setup(svc)
|
|
}
|
|
|
|
func (c *commandList) run(ctx context.Context, rep repo.Repository) error {
|
|
dir, err := snapshotfs.FilesystemDirectoryFromIDWithPath(ctx, rep, c.path, false)
|
|
if err != nil {
|
|
return errors.Wrap(err, "unable to get filesystem directory entry")
|
|
}
|
|
|
|
var prefix string
|
|
if !c.long {
|
|
prefix = c.path
|
|
if !strings.HasSuffix(prefix, "/") {
|
|
prefix += "/"
|
|
}
|
|
}
|
|
|
|
return c.listDirectory(ctx, dir, prefix, "")
|
|
}
|
|
|
|
func (c *commandList) listDirectory(ctx context.Context, d fs.Directory, prefix, indent string) error {
|
|
iter, err := d.Iterate(ctx)
|
|
if err != nil {
|
|
return err //nolint:wrapcheck
|
|
}
|
|
defer iter.Close()
|
|
|
|
e, err := iter.Next(ctx)
|
|
for e != nil {
|
|
if err2 := c.printDirectoryEntry(ctx, e, prefix, indent); err2 != nil {
|
|
return err2
|
|
}
|
|
|
|
e, err = iter.Next(ctx)
|
|
}
|
|
|
|
if err != nil {
|
|
return err //nolint:wrapcheck
|
|
}
|
|
|
|
if dws, ok := d.(fs.DirectoryWithSummary); ok && c.errorSummary {
|
|
if ds, _ := dws.Summary(ctx); ds != nil && ds.FatalErrorCount > 0 {
|
|
errorColor.Fprintf(c.out.stderr(), "\nNOTE: Encountered %v errors while snapshotting this directory:\n\n", ds.FatalErrorCount) //nolint:errcheck
|
|
|
|
for _, e := range ds.FailedEntries {
|
|
errorColor.Fprintf(c.out.stderr(), "- Error in \"%v\": %v\n", e.EntryPath, e.Error) //nolint:errcheck
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *commandList) printDirectoryEntry(ctx context.Context, e fs.Entry, prefix, indent string) error {
|
|
hoid, ok := e.(object.HasObjectID)
|
|
if !ok {
|
|
return errors.New("entry without object ID")
|
|
}
|
|
|
|
objectID := hoid.ObjectID()
|
|
oid := objectID.String()
|
|
col := defaultColor
|
|
|
|
var (
|
|
errorSummary string
|
|
info string
|
|
)
|
|
|
|
if dws, ok := e.(fs.DirectoryWithSummary); ok && c.errorSummary {
|
|
if ds, _ := dws.Summary(ctx); ds != nil && ds.FatalErrorCount > 0 {
|
|
errorSummary = fmt.Sprintf(" (%v errors)", ds.FatalErrorCount)
|
|
col = errorColor
|
|
}
|
|
}
|
|
|
|
switch {
|
|
case c.long:
|
|
info = fmt.Sprintf(
|
|
"%v %12s %v %-34v %v%v",
|
|
e.Mode(),
|
|
maybeHumanReadableBytes(c.humanReadable, e.Size()),
|
|
formatTimestamp(e.ModTime().Local()),
|
|
oid,
|
|
c.nameToDisplay(prefix, e),
|
|
errorSummary,
|
|
)
|
|
case c.showOID:
|
|
info = fmt.Sprintf("%-34v %v%v", oid, c.nameToDisplay(prefix, e), errorSummary)
|
|
|
|
default:
|
|
info = fmt.Sprintf("%v%v", c.nameToDisplay(prefix, e), errorSummary)
|
|
}
|
|
|
|
col.Fprintln(c.out.stdout(), info) //nolint:errcheck
|
|
|
|
if c.recursive {
|
|
if subdir, ok := e.(fs.Directory); ok {
|
|
if listerr := c.listDirectory(ctx, subdir, prefix+e.Name()+"/", indent+" "); listerr != nil {
|
|
return listerr
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *commandList) nameToDisplay(prefix string, e fs.Entry) string {
|
|
suffix := ""
|
|
if e.IsDir() {
|
|
suffix = "/"
|
|
}
|
|
|
|
if c.long || c.recursive {
|
|
return prefix + e.Name() + suffix
|
|
}
|
|
|
|
return e.Name()
|
|
}
|