]> Sergey Matveev's repositories - glocate.git/blob - diff.go
zfs-diff appends / to dataset roots
[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                 name = strings.TrimRight(name, "/")
65                 if name == "" {
66                         continue
67                 }
68                 name = "./" + name
69                 if isDir {
70                         name += "/"
71                 }
72                 switch cols[0] {
73                 case "-":
74                         delsNames = append(delsNames, name)
75                 case "+":
76                         addsNames = append(addsNames, name)
77                 case "M":
78                         modsNames = append(modsNames, name)
79                 case "R":
80                         if len(cols) != 4 {
81                                 log.Fatalln("bad zfs-diff format for R")
82                         }
83                         dst := "./" + deoctalize(strings.TrimPrefix(cols[3], strip))
84                         if isDir {
85                                 dst += "/"
86                                 rens = append(rens, Ren{
87                                         src: nameSplit(name),
88                                         dst: nameSplit(dst),
89                                 })
90                         } else {
91                                 delsNames = append(delsNames, name)
92                                 addsNames = append(addsNames, dst)
93                         }
94                 default:
95                         log.Fatalln("bad zfs-diff format")
96                 }
97         }
98
99         entsReader := make(chan Ent, 1<<10)
100         db, err := os.Open(dbPath)
101         if err != nil {
102                 log.Fatalln(err)
103         }
104         dels := make([][]string, 0, len(delsNames)+len(rens))
105         adds := make([][]string, 0, len(addsNames)+len(rens))
106         mods := make([]*Ent, 0, len(modsNames)+len(rens))
107         if len(rens) > 0 {
108                 sort.Sort(BySrc(rens))
109                 go reader(db, entsReader)
110                 var ent Ent
111                 var ok, met bool
112                 for {
113                         ent, ok = <-entsReader
114                         if !ok {
115                                 break
116                         }
117                 Retry:
118                         if len(rens) > 0 {
119                                 if hasPrefix(ent.name, rens[0].src) {
120                                         dels = append(dels, ent.name)
121                                         dst := append(
122                                                 append([]string{}, rens[0].dst...),
123                                                 ent.name[len(rens[0].src):]...,
124                                         )
125                                         adds = append(adds, dst)
126                                         mods = append(mods, &Ent{name: dst})
127                                         if !met {
128                                                 // strip "/" from prefix directory
129                                                 dst := rens[0].dst
130                                                 last := dst[len(dst)-1]
131                                                 dst[len(dst)-1] = last[:len(last)-1]
132                                                 met = true
133                                         }
134                                 } else if met {
135                                         met = false
136                                         rens = rens[1:]
137                                         goto Retry
138                                 }
139                         }
140                 }
141                 rens = nil
142         }
143
144         for _, name := range delsNames {
145                 dels = append(dels, nameSplit(name))
146         }
147         delsNames = nil
148         sort.Sort(ByName(dels))
149
150         for _, name := range addsNames {
151                 adds = append(adds, nameSplit(name))
152                 modsNames = append(modsNames, name)
153         }
154         addsNames = nil
155         sort.Sort(ByName(adds))
156
157         for _, name := range modsNames {
158                 mods = append(mods, &Ent{name: nameSplit(name)})
159         }
160         modsNames = nil
161         sort.Sort(EntByName(mods))
162         var info os.FileInfo
163         for _, ent := range mods {
164                 info, err = os.Stat(nameJoin(ent.name))
165                 if err != nil {
166                         log.Println("can not stat:", nameJoin(ent.name), ":", err)
167                         continue
168                 }
169                 if info.Mode().IsRegular() {
170                         ent.size = info.Size()
171                 }
172                 ent.mtime = info.ModTime().Unix()
173         }
174
175         _, err = db.Seek(0, io.SeekStart)
176         if err != nil {
177                 log.Fatalln(err)
178         }
179         tmp0, err := os.CreateTemp(TmpDir, "glocate-idx")
180         if err != nil {
181                 log.Fatalln(err)
182         }
183         defer os.Remove(tmp0.Name())
184         entsReader = make(chan Ent, 1<<10)
185         entsDirSizer := make(chan Ent, 1<<10)
186         entsWriter := make(chan Ent, 1<<10)
187         go reader(db, entsReader)
188
189         dirSizerJob := make(chan struct{})
190         var dirSizes []int64
191         sinkBack := make(chan Ent, 1)
192         go func() {
193                 dirSizer(&dirSizes, 1, sinkBack, entsDirSizer, entsWriter)
194                 close(dirSizerJob)
195         }()
196
197         writerJob := make(chan struct{})
198         go func() {
199                 writer(tmp0, entsWriter)
200                 close(writerJob)
201         }()
202
203         for ent := range entsReader {
204                 if len(dels) > 0 && namesCmp(ent.name, dels[0]) == 0 {
205                         dels = dels[1:]
206                         continue
207                 }
208                 for len(adds) > 0 && namesCmp(adds[0], ent.name) < 0 {
209                         if namesCmp(mods[0].name, adds[0]) != 0 {
210                                 panic("+ and M lists are out of sync")
211                         }
212                         newEnt := Ent{
213                                 name:  adds[0],
214                                 mtime: mods[0].mtime,
215                                 size:  mods[0].size,
216                         }
217                         entsDirSizer <- newEnt
218                         adds = adds[1:]
219                         mods = mods[1:]
220                 }
221                 if len(mods) > 0 && namesCmp(ent.name, mods[0].name) == 0 {
222                         ent.mtime = mods[0].mtime
223                         ent.size = mods[0].size
224                         mods = mods[1:]
225                 }
226                 entsDirSizer <- ent
227         }
228         for len(adds) > 0 {
229                 if namesCmp(mods[0].name, adds[0]) != 0 {
230                         panic("+ and M lists are out of sync")
231                 }
232                 newEnt := Ent{
233                         name:  adds[0],
234                         mtime: mods[0].mtime,
235                         size:  mods[0].size,
236                 }
237                 entsDirSizer <- newEnt
238                 adds = adds[1:]
239                 mods = mods[1:]
240         }
241
242         close(entsDirSizer)
243         <-dirSizerJob
244         close(entsWriter)
245         <-writerJob
246
247         tmp1 := applyDirSizes(tmp0, dirSizes)
248         tmp0.Close()
249         os.Remove(tmp0.Name())
250         return tmp1
251 }