hash.go (1790B)
1 package main 2 3 import ( 4 "io/ioutil" 5 "os" 6 "path/filepath" 7 "sort" 8 "sync" 9 "unicode" 10 11 "github.com/OneOfOne/xxhash" 12 "github.com/pkg/errors" 13 ) 14 15 // Hash recusively and concurrently hashes a fileEntry or directory. 16 func Hash(path string) (uint64, error) { 17 var ( 18 found = make(chan fileEntry) 19 wg = &sync.WaitGroup{} 20 ) 21 walk := func(path string, info os.FileInfo, err error) error { 22 if err != nil { 23 return err 24 } 25 if info.IsDir() { 26 return nil 27 } 28 wg.Add(1) 29 go func() { 30 defer wg.Done() 31 by, err := ioutil.ReadFile(path) 32 found <- fileEntry{ 33 Path: path, 34 Content: by, 35 Err: err, 36 } 37 }() 38 return nil 39 } 40 if err := filepath.Walk(path, walk); err != nil { 41 return 0, err 42 } 43 go func() { 44 wg.Wait() 45 close(found) 46 }() 47 var files []fileEntry 48 for f := range found { 49 if f.Err != nil { 50 return 0, f.Err 51 } 52 files = append(files, f) 53 } 54 sort.Sort(alphabetical(files)) 55 hash := xxhash.New64() 56 for _, f := range files { 57 if _, err := hash.Write(f.Content); err != nil { 58 return 0, errors.Wrap(err, "hashing") 59 } 60 } 61 return hash.Sum64(), nil 62 } 63 64 type fileEntry struct { 65 Path string 66 Content []byte 67 Err error 68 } 69 70 type alphabetical []fileEntry 71 72 func (a alphabetical) Len() int { 73 return len(a) 74 } 75 76 func (a alphabetical) Swap(ii, jj int) { 77 a[ii], a[jj] = a[jj], a[ii] 78 } 79 80 func (a alphabetical) Less(ii, jj int) bool { 81 iRunes := []rune(a[ii].Path) 82 jRunes := []rune(a[jj].Path) 83 max := len(iRunes) 84 if max > len(jRunes) { 85 max = len(jRunes) 86 } 87 for idx := 0; idx < max; idx++ { 88 ir := iRunes[idx] 89 jr := jRunes[idx] 90 lir := unicode.ToLower(ir) 91 ljr := unicode.ToLower(jr) 92 if lir != ljr { 93 return lir < ljr 94 } 95 // the lowercase runes are the same, so compare the original 96 if ir != jr { 97 return ir < jr 98 } 99 } 100 return false 101 }