Files

134 lines
2.4 KiB
Go

package main
import (
"crypto/md5"
"encoding/binary"
"encoding/hex"
"io"
"os"
"runtime"
"sync"
"github.com/schollz/progressbar/v3"
)
type DiffResult struct {
OnlyInOld []string
OnlyInNew []string
Changed []string
}
func safePartialMD5(path string) (string, error) {
f, err := os.Open(path)
if err != nil {
return "", err
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
return "", err
}
size := fi.Size()
h := md5.New()
binary.Write(h, binary.LittleEndian, size)
binary.Write(h, binary.LittleEndian, fi.Mode())
buf := make([]byte, 4096)
if size <= 16*1024 {
f.Seek(0, io.SeekStart)
io.Copy(h, f)
return hex.EncodeToString(h.Sum(nil)), nil
}
n, _ := f.Read(buf)
h.Write(buf[:n])
if size > 8192 {
mid := size / 2
f.Seek(mid-2048, io.SeekStart)
n, _ = f.Read(buf)
h.Write(buf[:n])
}
if size > 4096 {
f.Seek(-4096, io.SeekEnd)
n, _ = f.Read(buf)
h.Write(buf[:n])
}
return hex.EncodeToString(h.Sum(nil)), nil
}
func DiffFolders(oldPath, newPath string) (*DiffResult, error) {
oldFiles, err := collectFiles(oldPath)
if err != nil {
return nil, err
}
newFiles, err := collectFiles(newPath)
if err != nil {
return nil, err
}
result := &DiffResult{}
var mu sync.Mutex
var wg sync.WaitGroup
jobs := make(chan [3]string, 100)
total := 0
for rel := range oldFiles {
if _, ok := newFiles[rel]; ok {
total++
}
}
bar := progressbar.NewOptions(total,
progressbar.OptionSetDescription("🔍 Comparing files"),
progressbar.OptionSetWidth(30),
progressbar.OptionShowCount(),
progressbar.OptionSetPredictTime(true),
progressbar.OptionShowElapsedTimeOnFinish(),
)
workers := runtime.NumCPU() * 2
for i := 0; i < workers; i++ {
wg.Go(func() {
for job := range jobs {
rel, oldFile, newFile := job[0], job[1], job[2]
oldHash, _ := safePartialMD5(oldFile)
newHash, _ := safePartialMD5(newFile)
if oldHash != newHash {
mu.Lock()
result.Changed = append(result.Changed, rel)
mu.Unlock()
}
bar.Add(1)
}
})
}
for rel, oldFile := range oldFiles {
if newFile, ok := newFiles[rel]; ok {
jobs <- [3]string{rel, oldFile, newFile}
} else {
result.OnlyInOld = append(result.OnlyInOld, rel)
}
}
for rel := range newFiles {
if _, ok := oldFiles[rel]; !ok {
result.OnlyInNew = append(result.OnlyInNew, rel)
}
}
close(jobs)
wg.Wait()
bar.Finish()
return result, nil
}