diff --git a/src/pkg/backup/details/details.go b/src/pkg/backup/details/details.go index cf3e7b31a..8e3bf609e 100644 --- a/src/pkg/backup/details/details.go +++ b/src/pkg/backup/details/details.go @@ -383,6 +383,80 @@ func (d *Details) FilterEmptyContainers() *Details { } } +// FilterEmptyContainers returns a new Details struct all empty (ie: containing no +// items) stripped out. If meta files have not been filtered out already, they +// will continue to count as a "populated" container. +func (d *Details) FilterBoth() *Details { + type entCount struct { + ent DetailsEntry + itemCount int + } + + var ( + // shortRef: entCount + srec = map[string]entCount{} + items = []DetailsEntry{} + ) + + // split the entries into items and folders. + // folders are stored in a map by their shortRef for lookup. + for _, ent := range d.Entries { + if ent.isMetaFile() { + continue + } + + if ent.Folder == nil { + items = append(items, ent) + } else { + srec[ent.ShortRef] = entCount{ent, 0} + } + } + + // for every item, add a count to the owning folder. + // this assumes item parentRef == folder shortRef. + for _, ent := range items { + if len(ent.ParentRef) == 0 { + continue + } + + ec := srec[ent.ParentRef] + ec.itemCount++ + srec[ent.ParentRef] = ec + + // to maintain a hierarchical count so that we don't + // slice parent folders, this loop walks the tree upward + // by parent ref, adding one count to each parent up + // to the root. + parentRef := ec.ent.ParentRef + parentCount := 0 + + for len(parentRef) > 0 && parentCount == 0 { + ec := srec[parentRef] + + // minor optimization: if the parentCount is already + // >zero, then all of its parents are guaranteed >zero. + parentCount = ec.itemCount + + ec.itemCount++ + srec[parentRef] = ec + + parentRef = ec.ent.ParentRef + } + } + + // walk the map of folder entries; every folder with one or more + // items gets added back to the items slice to be returned. + for _, ec := range srec { + if ec.itemCount > 0 { + items = append(items, ec.ent) + } + } + + return &Details{ + DetailsModel: DetailsModel{items}, + } +} + // -------------------------------------------------------------------------------- // Entry // -------------------------------------------------------------------------------- diff --git a/src/pkg/backup/details/details_benchmark_test.go b/src/pkg/backup/details/details_benchmark_test.go index 573fdea2e..e23ba65e9 100644 --- a/src/pkg/backup/details/details_benchmark_test.go +++ b/src/pkg/backup/details/details_benchmark_test.go @@ -106,6 +106,9 @@ var result *Details func BenchmarkDetailsFiltering_512_both(b *testing.B) { benchmarkBoth(details512k, b) } func BenchmarkDetailsFiltering_256_both(b *testing.B) { benchmarkBoth(details256k, b) } func BenchmarkDetailsFiltering_102_both(b *testing.B) { benchmarkBoth(details102k, b) } +func BenchmarkDetailsFiltering_512_each(b *testing.B) { benchmarkEach(details512k, b) } +func BenchmarkDetailsFiltering_256_each(b *testing.B) { benchmarkEach(details256k, b) } +func BenchmarkDetailsFiltering_102_each(b *testing.B) { benchmarkEach(details102k, b) } func BenchmarkDetailsFiltering_512_meta(b *testing.B) { benchmarkMeta(details512k, b) } func BenchmarkDetailsFiltering_256_meta(b *testing.B) { benchmarkMeta(details256k, b) } func BenchmarkDetailsFiltering_102_meta(b *testing.B) { benchmarkMeta(details102k, b) } @@ -116,6 +119,16 @@ func BenchmarkDetailsFiltering_102_container(b *testing.B) { benchmarkContainer( func benchmarkBoth(d *Details, b *testing.B) { var d2 *Details + for n := 0; n < b.N; n++ { + d2 = d.FilterBoth() + } + + result = d2 +} + +func benchmarkEach(d *Details, b *testing.B) { + var d2 *Details + for n := 0; n < b.N; n++ { d2 = d.FilterMetaFiles().FilterEmptyContainers() }