diff --git a/cmd/ls-main.go b/cmd/ls-main.go index 58ceabb772..2ca975f6c2 100644 --- a/cmd/ls-main.go +++ b/cmd/ls-main.go @@ -48,6 +48,10 @@ var ( Name: "incomplete, I", Usage: "list incomplete uploads", }, + cli.BoolFlag{ + Name: "summarize", + Usage: "display summary information (number of objects, total size)", + }, } ) @@ -93,6 +97,9 @@ EXAMPLES: 8. List all contents versions if the bucket versioning is enabled. {{.Prompt}} {{.HelpName}} --versions s3/mybucket + + 9. List all objects on mybucket, summarize the number of objects and total size. + {{.Prompt}} {{.HelpName}} --summarize s3/mybucket/ `, } @@ -138,7 +145,7 @@ func parseRewindFlag(rewind string) (timeRef time.Time) { } // checkListSyntax - validate all the passed arguments -func checkListSyntax(ctx context.Context, cliCtx *cli.Context) ([]string, bool, bool, time.Time, bool) { +func checkListSyntax(ctx context.Context, cliCtx *cli.Context) ([]string, bool, bool, bool, time.Time, bool) { args := cliCtx.Args() if !cliCtx.Args().Present() { args = []string{"."} @@ -152,13 +159,14 @@ func checkListSyntax(ctx context.Context, cliCtx *cli.Context) ([]string, bool, isRecursive := cliCtx.Bool("recursive") isIncomplete := cliCtx.Bool("incomplete") withOlderVersions := cliCtx.Bool("versions") + isSummary := cliCtx.Bool("summarize") timeRef := parseRewindFlag(cliCtx.String("rewind")) if timeRef.IsZero() && withOlderVersions { timeRef = time.Now().UTC() } - return args, isRecursive, isIncomplete, timeRef, withOlderVersions + return args, isRecursive, isIncomplete, isSummary, timeRef, withOlderVersions } // mainList - is a handler for mc ls command @@ -175,9 +183,10 @@ func mainList(cliCtx *cli.Context) error { console.SetColor("Dir", color.New(color.FgCyan, color.Bold)) console.SetColor("Size", color.New(color.FgYellow)) console.SetColor("Time", color.New(color.FgGreen)) + console.SetColor("Summarize", color.New(color.Bold)) // check 'ls' cliCtx arguments. - args, isRecursive, isIncomplete, timeRef, withOlderVersions := checkListSyntax(ctx, cliCtx) + args, isRecursive, isIncomplete, isSummary, timeRef, withOlderVersions := checkListSyntax(ctx, cliCtx) var cErr error for _, targetURL := range args { @@ -192,8 +201,7 @@ func mainList(cliCtx *cli.Context) error { fatalIf(err.Trace(targetURL), "Unable to initialize target `"+targetURL+"`.") } } - - if e := doList(ctx, clnt, isRecursive, isIncomplete, timeRef, withOlderVersions); e != nil { + if e := doList(ctx, clnt, isRecursive, isIncomplete, isSummary, timeRef, withOlderVersions); e != nil { cErr = e } } diff --git a/cmd/ls.go b/cmd/ls.go index ddfc9455e1..f1f99acdff 100644 --- a/cmd/ls.go +++ b/cmd/ls.go @@ -171,8 +171,28 @@ func sortObjectVersions(ctntVersions []*ClientContent) { }) } +// summaryMessage container for summary message structure +type summaryMessage struct { + TotalObjects int64 `json:"totalObjects"` + TotalSize int64 `json:"totalSize"` +} + +// String colorized string message +func (s summaryMessage) String() string { + msg := console.Colorize("Summarize", fmt.Sprintf("\nTotal Size: %s", humanize.IBytes(uint64(s.TotalSize)))) + msg += "\n" + console.Colorize("Summarize", fmt.Sprintf("Total Objects: %d", s.TotalObjects)) + return msg +} + +// JSON jsonified summary message +func (s summaryMessage) JSON() string { + jsonMessageBytes, e := json.MarshalIndent(s, "", "") + fatalIf(probe.NewError(e), "Unable to marshal into JSON") + return string(jsonMessageBytes) +} + // Pretty print the list of versions belonging to one object -func printObjectVersions(clntURL ClientURL, ctntVersions []*ClientContent, printAllVersions bool) { +func printObjectVersions(clntURL ClientURL, ctntVersions []*ClientContent, printAllVersions, isSummary bool) { sortObjectVersions(ctntVersions) msgs := generateContentMessages(clntURL, ctntVersions, printAllVersions) for _, msg := range msgs { @@ -181,12 +201,14 @@ func printObjectVersions(clntURL ClientURL, ctntVersions []*ClientContent, print } // doList - list all entities inside a folder. -func doList(ctx context.Context, clnt Client, isRecursive, isIncomplete bool, timeRef time.Time, withOlderVersions bool) error { +func doList(ctx context.Context, clnt Client, isRecursive, isIncomplete, isSummary bool, timeRef time.Time, withOlderVersions bool) error { var ( lastPath string perObjectVersions []*ClientContent cErr error + totalSize int64 + totalObjects int64 ) for content := range clnt.List(ctx, ListOptions{ @@ -224,14 +246,24 @@ func doList(ctx context.Context, clnt Client, isRecursive, isIncomplete bool, ti if lastPath != content.URL.Path { // Print any object in the current list before reinitializing it - printObjectVersions(clnt.GetURL(), perObjectVersions, withOlderVersions) + printObjectVersions(clnt.GetURL(), perObjectVersions, withOlderVersions, isSummary) lastPath = content.URL.Path perObjectVersions = []*ClientContent{} } perObjectVersions = append(perObjectVersions, content) + totalSize += content.Size + totalObjects++ + } + + printObjectVersions(clnt.GetURL(), perObjectVersions, withOlderVersions, isSummary) + + if isSummary { + printMsg(summaryMessage{ + TotalObjects: totalObjects, + TotalSize: totalSize, + }) } - printObjectVersions(clnt.GetURL(), perObjectVersions, withOlderVersions) return cErr } diff --git a/cmd/tree-main.go b/cmd/tree-main.go index f89ae17ff7..102247953e 100644 --- a/cmd/tree-main.go +++ b/cmd/tree-main.go @@ -283,7 +283,7 @@ func mainTree(cliCtx *cli.Context) error { } clnt, err := newClientFromAlias(targetAlias, targetURL) fatalIf(err.Trace(targetURL), "Unable to initialize target `"+targetURL+"`.") - if e := doList(ctx, clnt, true, false, timeRef, false); e != nil { + if e := doList(ctx, clnt, true, false, false, timeRef, false); e != nil { cErr = e } }