]> Sergey Matveev's repositories - glocate.git/blob - diff.go
31712d63aaff055dea895b639dbc85323a6ab1e46d393ae7a232f2a4b80d9f1d
[glocate.git] / diff.go
1 package main
2
3 import (
4         "bufio"
5         "io"
6         "log"
7         "os"
8         "sort"
9         "strings"
10 )
11
12 type Ren struct {
13         src []string
14         dst []string
15 }
16
17 type BySrc []Ren
18
19 func (a BySrc) Len() int {
20         return len(a)
21 }
22
23 func (a BySrc) Swap(i, j int) {
24         a[i], a[j] = a[j], a[i]
25 }
26
27 func (a BySrc) Less(i, j int) bool {
28         return namesCmp(a[i].src, a[j].src) < 0
29 }
30
31 type EntByName []*Ent
32
33 func (a EntByName) Len() int {
34         return len(a)
35 }
36
37 func (a EntByName) Swap(i, j int) {
38         a[i], a[j] = a[j], a[i]
39 }
40
41 func (a EntByName) Less(i, j int) bool {
42         return namesCmp(a[i].name, a[j].name) < 0
43 }
44
45 func updateWithDiff(dbPath, strip string) *os.File {
46         scanner := bufio.NewScanner(os.Stdin)
47         var t string
48         var delsNames []string
49         var addsNames []string
50         var modsNames []string
51         var rens []Ren
52         var isDir bool
53         for scanner.Scan() {
54                 t = scanner.Text()
55                 if len(t) == 0 {
56                         continue
57                 }
58                 cols := strings.Split(t, "\t")
59                 if len(cols) < 3 {
60                         log.Fatalln("bad zfs-diff format")
61                 }
62                 isDir = cols[1] == "/"
63                 name := deoctalize(strings.TrimPrefix(cols[2], strip))
64                 if name == "" {
65                         continue
66                 }
67                 name = "./" + name
68                 if isDir {
69                         name += "/"
70                 }
71                 switch cols[0] {
72                 case "-":
73                         delsNames = append(delsNames, name)
74                 case "+":
75                         addsNames = append(addsNames, name)
76                 case "M":
77                         modsNames = append(modsNames, name)
78                 case "R":
79                         if len(cols) != 4 {
80                                 log.Fatalln("bad zfs-diff format for R")
81                         }
82                         dst := "./" + deoctalize(strings.TrimPrefix(cols[3], strip))
83                         if isDir {
84                                 dst += "/"
85                                 rens = append(rens, Ren{
86                                         src: nameSplit(name),
87                                         dst: nameSplit(dst),
88                                 })
89                         } else {
90                                 delsNames = append(delsNames, name)
91                                 addsNames = append(addsNames, dst)
92                         }
93                 default:
94                         log.Fatalln("bad zfs-diff format")
95                 }
96         }
97
98         entsReader := make(chan Ent, 1<<10)
99         db, err := os.Open(dbPath)
100         if err != nil {
101                 log.Fatalln(err)
102         }
103         dels := make([][]string, 0, len(delsNames)+len(rens))
104         adds := make([][]string, 0, len(addsNames)+len(rens))
105         mods := make([]*Ent, 0, len(modsNames)+len(rens))
106         if len(rens) > 0 {
107                 sort.Sort(BySrc(rens))
108                 go reader(db, entsReader)
109                 var ent Ent
110                 var ok, met bool
111                 for {
112                         ent, ok = <-entsReader
113                         if !ok {
114                                 break
115                         }
116                 Retry:
117                         if len(rens) > 0 {
118                                 if hasPrefix(ent.name, rens[0].src) {
119                                         dels = append(dels, ent.name)
120                                         dst := append(
121                                                 append([]string{}, rens[0].dst...),
122                                                 ent.name[len(rens[0].src):]...,
123                                         )
124                                         adds = append(adds, dst)
125                                         mods = append(mods, &Ent{name: dst})
126                                         if !met {
127                                                 // strip "/" from prefix directory
128                                                 dst := rens[0].dst
129                                                 last := dst[len(dst)-1]
130                                                 dst[len(dst)-1] = last[:len(last)-1]
131                                                 met = true
132                                         }
133                                 } else if met {
134                                         met = false
135                                         rens = rens[1:]
136                                         goto Retry
137                                 }
138                         }
139                 }
140                 rens = nil
141         }
142
143         for _, name := range delsNames {
144                 dels = append(dels, nameSplit(name))
145         }
146         delsNames = nil
147         sort.Sort(ByName(dels))
148
149         for _, name := range addsNames {
150                 adds = append(adds, nameSplit(name))
151                 modsNames = append(modsNames, name)
152         }
153         addsNames = nil
154         sort.Sort(ByName(adds))
155
156         for _, name := range modsNames {
157                 mods = append(mods, &Ent{name: nameSplit(name)})
158         }
159         modsNames = nil
160         sort.Sort(EntByName(mods))
161         var info os.FileInfo
162         for _, ent := range mods {
163                 info, err = os.Stat(nameJoin(ent.name))
164                 if err != nil {
165                         log.Println("can not stat:", nameJoin(ent.name), ":", err)
166                         continue
167                 }
168                 if info.Mode().IsRegular() {
169                         ent.size = info.Size()
170                 }
171                 ent.mtime = info.ModTime().Unix()
172         }
173
174         _, err = db.Seek(0, io.SeekStart)
175         if err != nil {
176                 log.Fatalln(err)
177         }
178         tmp0, err := os.CreateTemp(TmpDir, "glocate-idx")
179         if err != nil {
180                 log.Fatalln(err)
181         }
182         defer os.Remove(tmp0.Name())
183         entsReader = make(chan Ent, 1<<10)
184         entsDirSizer := make(chan Ent, 1<<10)
185         entsWriter := make(chan Ent, 1<<10)
186         go reader(db, entsReader)
187
188         dirSizerJob := make(chan struct{})
189         var dirSizes []int64
190         sinkBack := make(chan Ent, 1)
191         go func() {
192                 dirSizer(&dirSizes, 1, sinkBack, entsDirSizer, entsWriter)
193                 close(dirSizerJob)
194         }()
195
196         writerJob := make(chan struct{})
197         go func() {
198                 writer(tmp0, entsWriter)
199                 close(writerJob)
200         }()
201
202         for ent := range entsReader {
203                 if len(dels) > 0 && namesCmp(ent.name, dels[0]) == 0 {
204                         dels = dels[1:]
205                         continue
206                 }
207                 for len(adds) > 0 && namesCmp(adds[0], ent.name) < 0 {
208                         if namesCmp(mods[0].name, adds[0]) != 0 {
209                                 panic("+ and M lists are out of sync")
210                         }
211                         newEnt := Ent{
212                                 name:  adds[0],
213                                 mtime: mods[0].mtime,
214                                 size:  mods[0].size,
215                         }
216                         entsDirSizer <- newEnt
217                         adds = adds[1:]
218                         mods = mods[1:]
219                 }
220                 if len(mods) > 0 && namesCmp(ent.name, mods[0].name) == 0 {
221                         ent.mtime = mods[0].mtime
222                         ent.size = mods[0].size
223                         mods = mods[1:]
224                 }
225                 entsDirSizer <- ent
226         }
227         for len(adds) > 0 {
228                 if namesCmp(mods[0].name, adds[0]) != 0 {
229                         panic("+ and M lists are out of sync")
230                 }
231                 newEnt := Ent{
232                         name:  adds[0],
233                         mtime: mods[0].mtime,
234                         size:  mods[0].size,
235                 }
236                 entsDirSizer <- newEnt
237                 adds = adds[1:]
238                 mods = mods[1:]
239         }
240
241         close(entsDirSizer)
242         <-dirSizerJob
243         close(entsWriter)
244         <-writerJob
245
246         tmp1 := applyDirSizes(tmp0, dirSizes)
247         tmp0.Close()
248         os.Remove(tmp0.Name())
249         return tmp1
250 }