git » summer » main » tree

[main] / ui.go

package main

import (
	"flag"
	"fmt"
	"os"
	"runtime/debug"
	"sync"
	"time"
)

var (
	verbose = flag.Bool("v", false, "verbose mode (list each file)")
	quiet   = flag.Bool("q", false, "quiet mode")
)

func Verbosef(format string, args ...interface{}) {
	if *verbose {
		fmt.Printf(format+"\n", args...)
	}
}

func Printf(format string, args ...interface{}) {
	if !*quiet {
		fmt.Printf(format+"\n", args...)
	}
}

func Fatalf(format string, args ...interface{}) {
	fmt.Printf(format+"\n", args...)
	os.Exit(1)
}

func PrintVersion() {
	info, _ := debug.ReadBuildInfo()
	rev := info.Main.Version
	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)
}

type Progress struct {
	start time.Time
	isTTY bool

	wg sync.WaitGroup
	mu sync.Mutex

	matched, modified, missing, corrupted int64

	done chan bool
}

func NewProgress(isTTY bool) *Progress {
	p := &Progress{
		start: time.Now(),
		done:  make(chan bool),
		isTTY: isTTY,
	}
	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(true)
			return
		case <-ticker.C:
			if p.isTTY {
				p.print(false)
			}
		}
	}
}

func (p *Progress) print(last bool) {
	p.mu.Lock()
	defer p.mu.Unlock()

	prefix := "\r"
	suffix := ""
	if last {
		suffix = "\n"
	}

	// Usually we just overwrite the previous line.
	// But when verbose, just print one after the other.
	// For non-TTY, never overwrite.
	if *verbose || !p.isTTY {
		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 (p *Progress) PrintNew(path string, cs ChecksumV1) {
	p.mu.Lock()
	defer p.mu.Unlock()
	p.missing++
	Verbosef("%q: writing checksum (checksum:%x, mtime:%d)",
		path, cs.CRC32C, cs.ModTimeUsec)
}

func (p *Progress) PrintMissing(path string, cs *ChecksumV1) {
	p.mu.Lock()
	defer p.mu.Unlock()
	p.missing++
	if cs == nil {
		Verbosef("%q: missing checksum attribute", path)
	} else {
		Verbosef("%q: missing checksum attribute, adding it "+
			"(checksum:%x, mtime:%d)",
			path, cs.CRC32C, cs.ModTimeUsec)
	}
}

func (p *Progress) PrintModified(path string, old, new_ ChecksumV1) {
	p.mu.Lock()
	defer p.mu.Unlock()
	p.modified++
	Verbosef("%q: file modified (not corrupted) "+
		"(checksum: %x -> %x, mtime: %d -> %d)",
		path, old.CRC32C, new_.CRC32C, old.ModTimeUsec, new_.ModTimeUsec)
}

func (p *Progress) PrintMatched(path string, cs ChecksumV1) {
	p.mu.Lock()
	defer p.mu.Unlock()
	p.matched++
	Verbosef("%q: match (checksum:%x, mtime:%d)",
		path, cs.CRC32C, cs.ModTimeUsec)
}

type RepeatedStringFlag []string

func (f *RepeatedStringFlag) String() string {
	return fmt.Sprintf("%v", *f)
}

func (f *RepeatedStringFlag) Set(value string) error {
	*f = append(*f, value)
	return nil
}