github.com/anishathalye/periscope@v0.3.5/internal/periscope/report.go (about) 1 package periscope 2 3 import ( 4 "github.com/anishathalye/periscope/internal/db" 5 "github.com/anishathalye/periscope/internal/herror" 6 7 "container/list" 8 "fmt" 9 "path/filepath" 10 "sync" 11 12 "github.com/dustin/go-humanize" 13 ) 14 15 type ReportOptions struct { 16 Relative bool 17 } 18 19 func (ps *Periscope) Report(dir string, options *ReportOptions) herror.Interface { 20 var absDir string 21 if dir != "" { 22 var err herror.Interface 23 absDir, _, err = ps.checkFile(dir, false, true, "filter for", false, true) 24 if err != nil { 25 return err 26 } 27 } 28 // We stream duplicates with AllDuplicatesC, but we don't read directly 29 // from it and write results in the straightforward way. Writing to 30 // output may block (e.g. if the user is using a pager), so if a user 31 // had `psc report | less` open in one window and tried to `psc rm` in 32 // another, they'd get a "database is locked" error. This seems like 33 // it's a common enough use case that it's worth avoiding it. We 34 // achieve this by buffering the results in memory. 35 sets, err := ps.db.AllDuplicatesC(absDir) 36 if err != nil { 37 return err 38 } 39 40 buf := list.New() 41 done := false 42 var mu sync.Mutex 43 cond := sync.NewCond(&mu) 44 go func() { 45 for set := range sets { 46 mu.Lock() 47 buf.PushBack(set) 48 cond.Signal() 49 mu.Unlock() 50 } 51 mu.Lock() 52 done = true 53 cond.Signal() 54 mu.Unlock() 55 }() 56 57 var refDir string 58 if options.Relative { 59 var err error 60 refDir, err = filepath.Abs(dir) // if dir == "", this will treat it like dir = "." 61 if err != nil { 62 return herror.Internal(err, "") 63 } 64 } 65 first := true 66 for { 67 mu.Lock() 68 for !done && buf.Len() == 0 { 69 cond.Wait() 70 } 71 if done && buf.Len() == 0 { 72 mu.Unlock() 73 break 74 } 75 front := buf.Front() 76 set := front.Value.(db.DuplicateSet) 77 buf.Remove(front) 78 mu.Unlock() 79 80 if !first { 81 fmt.Fprintf(ps.outStream, "\n") 82 } 83 fmt.Fprintf(ps.outStream, "%s\n", humanize.Bytes(uint64(set[0].Size))) // all files within a set have the same size 84 for _, info := range set { 85 path := info.Path 86 if options.Relative { 87 path = relPath(refDir, path) 88 } 89 fmt.Fprintf(ps.outStream, " %s\n", path) 90 } 91 first = false 92 } 93 94 return nil 95 }