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