package main import ( "bufio" "io" "log" "os" "sort" "strings" ) type Ren struct { src []string dst []string } type BySrc []Ren func (a BySrc) Len() int { return len(a) } func (a BySrc) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a BySrc) Less(i, j int) bool { return namesCmp(a[i].src, a[j].src) < 0 } type EntByName []*Ent func (a EntByName) Len() int { return len(a) } func (a EntByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a EntByName) Less(i, j int) bool { return namesCmp(a[i].name, a[j].name) < 0 } func updateWithDiff(dbPath, strip string) *os.File { scanner := bufio.NewScanner(os.Stdin) var t string var delsNames []string var addsNames []string var modsNames []string var rens []Ren var isDir bool for scanner.Scan() { t = scanner.Text() if len(t) == 0 { continue } cols := strings.Split(t, "\t") if len(cols) < 3 { log.Fatalln("bad zfs-diff format") } isDir = cols[1] == "/" name := deoctalize(strings.TrimPrefix(cols[2], strip)) if name == "" { continue } name = "./" + name if isDir { name += "/" } switch cols[0] { case "-": delsNames = append(delsNames, name) case "+": addsNames = append(addsNames, name) case "M": modsNames = append(modsNames, name) case "R": if len(cols) != 4 { log.Fatalln("bad zfs-diff format for R") } dst := "./" + deoctalize(strings.TrimPrefix(cols[3], strip)) if isDir { dst += "/" rens = append(rens, Ren{ src: nameSplit(name), dst: nameSplit(dst), }) } else { delsNames = append(delsNames, name) addsNames = append(addsNames, dst) } default: log.Fatalln("bad zfs-diff format") } } entsReader := make(chan Ent, 1<<10) db, err := os.Open(dbPath) if err != nil { log.Fatalln(err) } dels := make([][]string, 0, len(delsNames)+len(rens)) adds := make([][]string, 0, len(addsNames)+len(rens)) mods := make([]*Ent, 0, len(modsNames)+len(rens)) if len(rens) > 0 { sort.Sort(BySrc(rens)) go reader(db, entsReader) var ent Ent var ok, met bool for { ent, ok = <-entsReader if !ok { break } Retry: if len(rens) > 0 { if hasPrefix(ent.name, rens[0].src) { dels = append(dels, ent.name) dst := append( append([]string{}, rens[0].dst...), ent.name[len(rens[0].src):]..., ) adds = append(adds, dst) mods = append(mods, &Ent{name: dst}) if !met { // strip "/" from prefix directory dst := rens[0].dst last := dst[len(dst)-1] dst[len(dst)-1] = last[:len(last)-1] met = true } } else if met { met = false rens = rens[1:] goto Retry } } } rens = nil } for _, name := range delsNames { dels = append(dels, nameSplit(name)) } delsNames = nil sort.Sort(ByName(dels)) for _, name := range addsNames { adds = append(adds, nameSplit(name)) modsNames = append(modsNames, name) } addsNames = nil sort.Sort(ByName(adds)) for _, name := range modsNames { mods = append(mods, &Ent{name: nameSplit(name)}) } modsNames = nil sort.Sort(EntByName(mods)) var info os.FileInfo for _, ent := range mods { info, err = os.Stat(nameJoin(ent.name)) if err != nil { log.Println("can not stat:", nameJoin(ent.name), ":", err) continue } if info.Mode().IsRegular() { ent.size = info.Size() } ent.mtime = info.ModTime().Unix() } _, err = db.Seek(0, io.SeekStart) if err != nil { log.Fatalln(err) } tmp0, err := os.CreateTemp(TmpDir, "glocate-idx") if err != nil { log.Fatalln(err) } defer os.Remove(tmp0.Name()) entsReader = make(chan Ent, 1<<10) entsDirSizer := make(chan Ent, 1<<10) entsWriter := make(chan Ent, 1<<10) go reader(db, entsReader) dirSizerJob := make(chan struct{}) var dirSizes []int64 sinkBack := make(chan Ent, 1) go func() { dirSizer(&dirSizes, 1, sinkBack, entsDirSizer, entsWriter) close(dirSizerJob) }() writerJob := make(chan struct{}) go func() { writer(tmp0, entsWriter) close(writerJob) }() for ent := range entsReader { if len(dels) > 0 && namesCmp(ent.name, dels[0]) == 0 { dels = dels[1:] continue } for len(adds) > 0 && namesCmp(adds[0], ent.name) < 0 { if namesCmp(mods[0].name, adds[0]) != 0 { panic("+ and M lists are out of sync") } newEnt := Ent{ name: adds[0], mtime: mods[0].mtime, size: mods[0].size, } entsDirSizer <- newEnt adds = adds[1:] mods = mods[1:] } if len(mods) > 0 && namesCmp(ent.name, mods[0].name) == 0 { ent.mtime = mods[0].mtime ent.size = mods[0].size mods = mods[1:] } entsDirSizer <- ent } for len(adds) > 0 { if namesCmp(mods[0].name, adds[0]) != 0 { panic("+ and M lists are out of sync") } newEnt := Ent{ name: adds[0], mtime: mods[0].mtime, size: mods[0].size, } entsDirSizer <- newEnt adds = adds[1:] mods = mods[1:] } close(entsDirSizer) <-dirSizerJob close(entsWriter) <-writerJob tmp1 := applyDirSizes(tmp0, dirSizes) tmp0.Close() os.Remove(tmp0.Name()) return tmp1 }