From f577f5521fe4a346f83b1f6e3cb934e641554fcf Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Fri, 4 Nov 2022 11:24:19 -0600 Subject: [PATCH] server(filesystem): fix panic with archive Progress --- server/filesystem/archive.go | 38 +++++++++++++++++------- server/filesystem/archive_test.go | 48 +++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 11 deletions(-) create mode 100644 server/filesystem/archive_test.go diff --git a/server/filesystem/archive.go b/server/filesystem/archive.go index 206907a..cd19ad5 100644 --- a/server/filesystem/archive.go +++ b/server/filesystem/archive.go @@ -69,20 +69,36 @@ func (p *Progress) Write(v []byte) (int, error) { // Progress returns a formatted progress string for the current progress. func (p *Progress) Progress(width int) string { + // current = 100 (Progress, dynamic) + // total = 1000 (Content-Length, dynamic) + // width = 25 (Number of ticks to display, static) + // widthPercentage = 100 / width (What percentage does each tick represent, static) + // + // percentageDecimal = current / total = 0.1 + // percentage = percentageDecimal * 100 = 10% + // ticks = percentage / widthPercentage = 2.5 + // + // ticks is a float64, so we cast it to an int which rounds it down to 2. + + // Values are cast to floats to prevent integer division. current := p.Written() total := p.Total() + // width := is passed as a parameter + widthPercentage := float64(100) / float64(width) + percentageDecimal := float64(current) / float64(total) + percentage := percentageDecimal * 100 + ticks := int(percentage / widthPercentage) - // v = 100 (Progress) - // size = 1000 (Content-Length) - // p / size = 0.1 - // * 100 = 10% (Multiply by 100 to get a percentage of the download) - // 10% / tickPercentage = (10% / (100 / 25)) (Divide by tick percentage to get the number of ticks) - // 2.5 (Number of ticks as a float64) - // 2 (convert to an integer) + // Ensure that we never get a negative number of ticks, this will prevent strings#Repeat + // from panicking. A negative number of ticks is likely to happen when the total size is + // inaccurate, such as when we are going off of rough disk usage calculation. + if ticks < 0 { + ticks = 0 + } else if ticks > width { + ticks = width + } - // We have to cast these numbers to float in order to get a float result from the division. - ticks := ((float64(current) / float64(total)) * 100) / (float64(100) / float64(width)) - bar := strings.Repeat("=", int(ticks)) + strings.Repeat(" ", width-int(ticks)) + bar := strings.Repeat("=", ticks) + strings.Repeat(" ", width-ticks) return "[" + bar + "] " + system.FormatBytes(current) + " / " + system.FormatBytes(total) } @@ -255,7 +271,7 @@ func (a *Archive) addToArchive(p string, rp string, w *tar.Writer) error { // it doesn't work. target, err = os.Readlink(s.Name()) if err != nil { - // Ignore the not exist errors specifically, since theres nothing important about that. + // Ignore the not exist errors specifically, since there is nothing important about that. if !os.IsNotExist(err) { log.WithField("path", rp).WithField("readlink_err", err.Error()).Warn("failed reading symlink for target path; skipping...") } diff --git a/server/filesystem/archive_test.go b/server/filesystem/archive_test.go new file mode 100644 index 0000000..f6739f8 --- /dev/null +++ b/server/filesystem/archive_test.go @@ -0,0 +1,48 @@ +package filesystem + +import ( + "bytes" + "testing" + + . "github.com/franela/goblin" +) + +func TestProgress(t *testing.T) { + g := Goblin(t) + + g.Describe("Progress", func() { + g.It("properly initializes", func() { + total := int64(1000) + p := NewProgress(total) + g.Assert(p).IsNotNil() + g.Assert(p.Total()).Equal(total) + g.Assert(p.Written()).Equal(int64(0)) + }) + + g.It("increments written when Write is called", func() { + v := []byte("hello") + p := NewProgress(1000) + _, err := p.Write(v) + g.Assert(err).IsNil() + g.Assert(p.Written()).Equal(int64(len(v))) + }) + + g.It("renders a progress bar", func() { + v := bytes.Repeat([]byte{' '}, 100) + p := NewProgress(1000) + _, err := p.Write(v) + g.Assert(err).IsNil() + g.Assert(p.Written()).Equal(int64(len(v))) + g.Assert(p.Progress(25)).Equal("[== ] 100 B / 1000 B") + }) + + g.It("renders a progress bar when written exceeds total", func() { + v := bytes.Repeat([]byte{' '}, 1001) + p := NewProgress(1000) + _, err := p.Write(v) + g.Assert(err).IsNil() + g.Assert(p.Written()).Equal(int64(len(v))) + g.Assert(p.Progress(25)).Equal("[=========================] 1001 B / 1000 B") + }) + }) +}