author | Alberto Bertogli
<albertito@blitiri.com.ar> 2023-04-03 01:25:19 UTC |
committer | Alberto Bertogli
<albertito@blitiri.com.ar> 2023-04-03 01:39:20 UTC |
parent | cb0d27e09312054136fde427015cf6f1ab3529f2 |
summer.go | +21 | -27 |
test/access.t | +6 | -3 |
test/basic.t | +24 | -13 |
test/sqlite.t | +9 | -5 |
ui.go | +105 | -24 |
diff --git a/summer.go b/summer.go index 2375457..c31ac59 100644 --- a/summer.go +++ b/summer.go @@ -118,7 +118,8 @@ func openAndInfo(path string, d fs.DirEntry) (*os.File, fs.FileInfo, error) { } func generate(db DB, root string) error { - var total int64 + p := NewProgress() + defer p.Stop() fn := func(path string, d fs.DirEntry, err error) error { if !isFileRelevant(path, d, err) { return err @@ -146,17 +147,18 @@ func generate(db DB, root string) error { return err } - total++ + p.PrintNew(path) return nil } err := filepath.WalkDir(root, fn) - PrintWritten(total) return err } func verify(db DB, root string) error { - var missing, modified, corrupted, matched int64 + p := NewProgress() + defer p.Stop() + fn := func(path string, d fs.DirEntry, err error) error { if !isFileRelevant(path, d, err) { return err @@ -173,8 +175,7 @@ func verify(db DB, root string) error { return err } if !hasAttr { - PrintMissing(path) - missing++ + p.PrintMissing(path) return nil } @@ -195,30 +196,28 @@ func verify(db DB, root string) error { } if csumFromFile.ModTimeUsec != csumComputed.ModTimeUsec { - PrintModified(path) - modified++ + p.PrintModified(path) } else if csumFromFile.CRC32C != csumComputed.CRC32C { - PrintCorrupted(path, csumFromFile, csumComputed) - corrupted++ + p.PrintCorrupted(path, csumFromFile, csumComputed) } else { - PrintMatched(path) - matched++ + p.PrintMatched(path) } return nil } err := filepath.WalkDir(root, fn) - PrintSummary(matched, modified, missing, corrupted) - if corrupted > 0 && err == nil { - err = fmt.Errorf("detected %d corrupted files", corrupted) + if p.corrupted > 0 && err == nil { + err = fmt.Errorf("detected %d corrupted files", p.corrupted) } return err } func update(db DB, root string) error { - var missing, modified, corrupted, matched int64 + p := NewProgress() + defer p.Stop() + fn := func(path string, d fs.DirEntry, err error) error { if !isFileRelevant(path, d, err) { return err @@ -249,8 +248,7 @@ func update(db DB, root string) error { } if !hasAttr { // Attribute is missing. Expected for newly created files. - PrintMissing(path) - missing++ + p.PrintMissing(path) return db.Write(fd, csumComputed) } @@ -261,25 +259,21 @@ func update(db DB, root string) error { if csumFromFile.ModTimeUsec != csumComputed.ModTimeUsec { // File modified. Expected for updated files. - PrintModified(path) - modified++ + p.PrintModified(path) return db.Write(fd, csumComputed) } else if csumFromFile.CRC32C != csumComputed.CRC32C { - PrintCorrupted(path, csumFromFile, csumComputed) - corrupted++ + p.PrintCorrupted(path, csumFromFile, csumComputed) } else { - PrintMatched(path) - matched++ + p.PrintMatched(path) } return nil } err := filepath.WalkDir(root, fn) - PrintSummary(matched, modified, missing, corrupted) - if corrupted > 0 && err == nil { - err = fmt.Errorf("detected %d corrupted files", corrupted) + if p.corrupted > 0 && err == nil { + err = fmt.Errorf("detected %d corrupted files", p.corrupted) } return err } diff --git a/test/access.t b/test/access.t index c7d28ef..b0893b9 100644 --- a/test/access.t +++ b/test/access.t @@ -9,12 +9,15 @@ interfere. $ echo marola > root/hola $ summer -db=db.sqlite3 generate root/ - 2 checksums written + \r (no-eol) (esc) + 0s: 0 matched, 0 modified, 2 new, 0 corrupted $ summer -db=db.sqlite3 verify root/ - 2 matched, 0 modified, 0 new, 0 corrupted + \r (no-eol) (esc) + 0s: 2 matched, 0 modified, 0 new, 0 corrupted $ chmod 0000 root/empty $ summer -db=db.sqlite3 verify root/ - 0 matched, 0 modified, 0 new, 0 corrupted + \r (no-eol) (esc) + 0s: 0 matched, 0 modified, 0 new, 0 corrupted open root/empty: permission denied [1] diff --git a/test/basic.t b/test/basic.t index 5e3d48c..124a812 100644 --- a/test/basic.t +++ b/test/basic.t @@ -10,32 +10,40 @@ Generate test data. Generate and verify. $ summer generate . - 2 checksums written + \r (no-eol) (esc) + 0s: 0 matched, 0 modified, 2 new, 0 corrupted + $ summer verify . - 2 matched, 0 modified, 0 new, 0 corrupted + \r (no-eol) (esc) + 0s: 2 matched, 0 modified, 0 new, 0 corrupted Check handling of new and updated files. $ echo trova > nueva $ touch empty $ summer verify . - 1 matched, 1 modified, 1 new, 0 corrupted + \r (no-eol) (esc) + 0s: 1 matched, 1 modified, 1 new, 0 corrupted $ summer update . - 1 matched, 1 modified, 1 new, 0 corrupted + \r (no-eol) (esc) + 0s: 1 matched, 1 modified, 1 new, 0 corrupted $ summer verify . - 3 matched, 0 modified, 0 new, 0 corrupted + \r (no-eol) (esc) + 0s: 3 matched, 0 modified, 0 new, 0 corrupted Corrupt a file by changing its contents without changing the mtime. $ OLD_MTIME=`stat -c "%y" hola` $ echo sospechoso >> hola $ summer verify . - 2 matched, 1 modified, 0 new, 0 corrupted + \r (no-eol) (esc) + 0s: 2 matched, 1 modified, 0 new, 0 corrupted $ touch --date="$OLD_MTIME" hola $ summer verify . "hola": FILE CORRUPTED - expected:239059f6, got:916db13f - 2 matched, 0 modified, 0 new, 1 corrupted + \r (no-eol) (esc) + 0s: 2 matched, 0 modified, 0 new, 1 corrupted detected 1 corrupted files [1] @@ -44,16 +52,19 @@ it. $ summer update . "hola": FILE CORRUPTED - expected:239059f6, got:916db13f - 2 matched, 0 modified, 0 new, 1 corrupted + \r (no-eol) (esc) + 0s: 2 matched, 0 modified, 0 new, 1 corrupted detected 1 corrupted files [1] But "generate" does override it. $ summer generate . - 3 checksums written + \r (no-eol) (esc) + 0s: 0 matched, 0 modified, 3 new, 0 corrupted $ summer verify . - 3 matched, 0 modified, 0 new, 0 corrupted + \r (no-eol) (esc) + 0s: 3 matched, 0 modified, 0 new, 0 corrupted Check verbose and quiet. @@ -61,7 +72,7 @@ Check verbose and quiet. "empty": match "hola": match "nueva": match - 3 matched, 0 modified, 0 new, 0 corrupted + 0s: 3 matched, 0 modified, 0 new, 0 corrupted $ summer -q verify . $ summer -q generate . $ summer -q update . @@ -74,7 +85,7 @@ Check that symlinks are ignored. "empty": match "hola": match "nueva": match - 3 matched, 0 modified, 0 new, 0 corrupted + 0s: 3 matched, 0 modified, 0 new, 0 corrupted Check that the root path doesn't confuse us. @@ -82,4 +93,4 @@ Check that the root path doesn't confuse us. "/.*/empty": match (re) "/.*/hola": match (re) "/.*/nueva": match (re) - 3 matched, 0 modified, 0 new, 0 corrupted + 0s: 3 matched, 0 modified, 0 new, 0 corrupted diff --git a/test/sqlite.t b/test/sqlite.t index 66fae16..a011de9 100644 --- a/test/sqlite.t +++ b/test/sqlite.t @@ -7,11 +7,14 @@ This is enough to exercise the backend. $ echo marola > hola $ summer -db=db.sqlite3 generate . - 3 checksums written + \r (no-eol) (esc) + 0s: 0 matched, 0 modified, 3 new, 0 corrupted $ summer -db=db.sqlite3 verify . - 2 matched, 1 modified, 0 new, 0 corrupted + \r (no-eol) (esc) + 0s: 2 matched, 1 modified, 0 new, 0 corrupted $ summer -db=db.sqlite3 update . - 2 matched, 1 modified, 0 new, 0 corrupted + \r (no-eol) (esc) + 0s: 2 matched, 1 modified, 0 new, 0 corrupted Check that the root path doesn't confuse us. @@ -19,12 +22,13 @@ Check that the root path doesn't confuse us. ".*/db.sqlite3": file modified \(not corrupted\), updating (re) ".*/empty": match (re) ".*/hola": match (re) - 2 matched, 1 modified, 0 new, 0 corrupted + 0s: 2 matched, 1 modified, 0 new, 0 corrupted Force a write error to check it is appropriately handled. $ summer "-db=file:db.sqlite3?mode=ro" generate . - . checksums written (re) + \r (no-eol) (esc) + 0s: 0 matched, 0 modified, 0 new, 0 corrupted attempt to write a readonly database [1] diff --git a/ui.go b/ui.go index ca0d49c..b3442d0 100644 --- a/ui.go +++ b/ui.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "runtime/debug" + "sync" "time" ) @@ -30,43 +31,123 @@ func Fatalf(format string, args ...interface{}) { os.Exit(1) } -func PrintWritten(written int64) { - Printf("%d checksums written", written) +func PrintVersion() { + info, _ := debug.ReadBuildInfo() + rev := "" + ts := time.Time{} + for _, s := range info.Settings { + switch s.Key { + case "vcs.revision": + rev = s.Value + case "vcs.time": + ts, _ = time.Parse(time.RFC3339, s.Value) + } + } + Printf("summer version %s (%s)", rev, ts) } -func PrintSummary(matched, modified, missing, corrupted int64) { - Printf("%d matched, %d modified, %d new, %d corrupted", - matched, modified, missing, corrupted) +type Progress struct { + start time.Time + + wg sync.WaitGroup + mu sync.Mutex + + matched, modified, missing, corrupted int64 + + done chan bool } -func PrintCorrupted(path string, expected, got ChecksumV1) { +func NewProgress() *Progress { + p := &Progress{ + start: time.Now(), + done: make(chan bool), + } + p.wg.Add(1) + go p.periodicPrint() + return p +} + +func (p *Progress) Stop() { + p.done <- true + p.wg.Wait() +} + +func (p *Progress) periodicPrint() { + defer p.wg.Done() + ticker := time.NewTicker(250 * time.Millisecond) + defer ticker.Stop() + + if *quiet { + <-p.done + return + } + + for { + select { + case <-p.done: + p.print() + if !*verbose { + fmt.Printf("\n") + } + return + case <-ticker.C: + p.print() + } + } +} + +func (p *Progress) print() { + p.mu.Lock() + defer p.mu.Unlock() + + // Usually we just overwrite the previous line. + // But when verbose, just print them. + prefix := "\r" + suffix := "" + if *verbose { + prefix = "" + suffix = "\n" + } + + fmt.Printf( + prefix+"%v: %d matched, %d modified, %d new, %d corrupted"+suffix, + time.Since(p.start).Round(time.Second), + p.matched, p.modified, p.missing, p.corrupted, + ) +} + +func (p *Progress) PrintCorrupted(path string, expected, got ChecksumV1) { + p.mu.Lock() + defer p.mu.Unlock() + p.corrupted++ Printf("%q: FILE CORRUPTED - expected:%x, got:%x", path, expected.CRC32C, got.CRC32C) } -func PrintMissing(path string) { +func (p *Progress) PrintNew(path string) { + p.mu.Lock() + defer p.mu.Unlock() + p.missing++ + Verbosef("%q: adding checksum", path) +} + +func (p *Progress) PrintMissing(path string) { + p.mu.Lock() + defer p.mu.Unlock() + p.missing++ Verbosef("%q: missing checksum attribute, adding it", path) } -func PrintModified(path string) { +func (p *Progress) PrintModified(path string) { + p.mu.Lock() + defer p.mu.Unlock() + p.modified++ Verbosef("%q: file modified (not corrupted), updating", path) } -func PrintMatched(path string) { +func (p *Progress) PrintMatched(path string) { + p.mu.Lock() + defer p.mu.Unlock() + p.matched++ Verbosef("%q: match", path) } - -func PrintVersion() { - info, _ := debug.ReadBuildInfo() - rev := "" - ts := time.Time{} - for _, s := range info.Settings { - switch s.Key { - case "vcs.revision": - rev = s.Value - case "vcs.time": - ts, _ = time.Parse(time.RFC3339, s.Value) - } - } - Printf("summer version %s (%s)", rev, ts) -}