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  }