logs.go
  1  package main
  2  
  3  import (
  4  	"fmt"
  5  	"io/fs"
  6  	"os"
  7  	"os/exec"
  8  	"path/filepath"
  9  	"sort"
 10  	"strings"
 11  	"time"
 12  )
 13  
 14  // Color returns a HTML color code between green and yellow based on the
 15  // number of days that passed since ds.
 16  func (ds DateString) Color() string {
 17  	date, _ := time.Parse("2006-01-02T15:04:05Z", string(ds))
 18  	days := int(time.Since(date).Hours() / 24)
 19  	if days > 255 {
 20  		days = 255
 21  	}
 22  	return fmt.Sprintf("#%02xff00", days)
 23  }
 24  
 25  func fetchLogs(dirs chan<- NamedFS) {
 26  	err := fs.WalkDir(bsdirFS, ".", func(path string, d fs.DirEntry, err error) error {
 27  		if err != nil {
 28  			return err
 29  		}
 30  		if path[0] == '.' {
 31  			return nil
 32  		}
 33  		if d.IsDir() && len(strings.Split(path, string(filepath.Separator))) == 4 {
 34  			dirs <- NamedFS{
 35  				FS:   bsdirFS,
 36  				Name: path,
 37  			}
 38  		}
 39  		return nil
 40  	})
 41  	if err != nil {
 42  		fmt.Fprintf(os.Stderr, "Reading logs failed: %v\n", err)
 43  	}
 44  	close(dirs)
 45  }
 46  
 47  func collectLogs(dirs <-chan NamedFS) {
 48  	data.Logs = make(map[Timeframe][]Log)
 49  	data.VendorBoardDate = make(map[string]DateString)
 50  	data.VendorBoardReferenced = make(map[string]bool)
 51  	timeframes := make(map[Timeframe]bool)
 52  	gitcache := make(map[string]string)
 53  	for dir := range dirs {
 54  		upstream := ""
 55  		revB, err := fs.ReadFile(dir.FS, filepath.Join(dir.Name, "revision.txt"))
 56  		if err != nil {
 57  			continue
 58  		}
 59  		rev := string(revB)
 60  		skipDir := false
 61  		for _, line := range strings.Split(rev, "\n") {
 62  			item := strings.SplitN(line, ":", 2)
 63  			if len(item) != 2 {
 64  				// This is an error, but let's try to extract
 65  				// as much value out of revision.txt files as
 66  				// possible, even if some lines are erroneous.
 67  				continue
 68  			}
 69  			if item[0] == "Upstream revision" {
 70  				upstream = strings.TrimSpace(item[1])
 71  				// tried using go-git, but its resolver
 72  				// couldn't expand short hashes despite the
 73  				// docs claiming that it can.
 74  				if val, ok := gitcache[upstream]; ok {
 75  					upstream = val
 76  				} else {
 77  					res, err := exec.Command("/usr/bin/git", "-C", cbdir, "log", "-n1", "--format=%H", upstream).Output()
 78  					if err != nil {
 79  						fmt.Fprintf(os.Stderr, "revision %s not found \n", upstream)
 80  						skipDir = true
 81  						break
 82  					}
 83  					gitcache[upstream] = strings.TrimSpace(string(res))
 84  					upstream = gitcache[upstream]
 85  				}
 86  			}
 87  		}
 88  		if skipDir {
 89  			continue
 90  		}
 91  
 92  		rawFiles, err := fs.Glob(dir.FS, filepath.Join(dir.Name, "*"))
 93  		if err != nil {
 94  			fmt.Fprintf(os.Stderr, "Could not fetch log data, skipping: %v\n", err)
 95  			continue
 96  		}
 97  
 98  		pieces := strings.Split(dir.Name, string(filepath.Separator))
 99  		if len(pieces) < 4 {
100  			fmt.Fprintf(os.Stderr, "log directory %s is malformed, skipping\n", dir.Name)
101  			continue
102  		}
103  		vendorBoard := strings.Join(pieces[:2], "/")
104  		// TODO: these need to become "second to last" and "last" item
105  		// but only after compatibility to the current system has been ensured.
106  		dateTimePath := pieces[3]
107  		dateTime, err := time.Parse(time.RFC3339, strings.ReplaceAll(dateTimePath, "_", ":"))
108  		if err != nil {
109  			fmt.Fprintf(os.Stderr, "Could not parse timestamp from %s: %v\n", dir.Name, err)
110  			continue
111  		}
112  		dateTimeNormal := dateTime.UTC().Format("2006-01-02T15:04:05Z")
113  		dateTimeHuman := dateTime.UTC().Format(time.UnixDate)
114  		tfYear, tfWeek := dateTime.ISOWeek()
115  		timeframe := Timeframe(fmt.Sprintf("%dW%02d", tfYear, tfWeek))
116  
117  		if !timeframes[timeframe] {
118  			timeframes[timeframe] = true
119  			data.Timeframes = append(data.Timeframes, timeframe)
120  			data.Logs[timeframe] = []Log{}
121  		}
122  
123  		files := []Path{}
124  		l := len(dir.Name) + 1
125  		for _, file := range rawFiles {
126  			if file[l:] == "revision.txt" {
127  				continue
128  			}
129  			files = append(files, Path{
130  				Path:     dir.Name + "/",
131  				Basename: file[l:],
132  			})
133  		}
134  
135  		data.Logs[timeframe] = append(data.Logs[timeframe], Log{
136  			VendorBoard:  vendorBoard,
137  			Time:         dateTimeNormal,
138  			TimeReadable: dateTimeHuman,
139  			Upstream:     upstream,
140  			Files:        files,
141  		})
142  	}
143  	sort.Slice(data.Timeframes, func(i, j int) bool {
144  		// reverse sort
145  		return data.Timeframes[i] > data.Timeframes[j]
146  	})
147  	for bi := range data.Logs {
148  		bucket := data.Logs[bi]
149  		sort.Slice(data.Logs[bi], func(i, j int) bool {
150  			if bucket[i].Time == bucket[j].Time {
151  				return (bucket[i].VendorBoard > bucket[j].VendorBoard)
152  			}
153  			return (bucket[i].Time > bucket[j].Time)
154  		})
155  	}
156  	for _, ts := range data.Timeframes {
157  		for li, l := range data.Logs[ts] {
158  			if _, match := data.VendorBoardDate[l.VendorBoard]; match {
159  				continue
160  			}
161  			data.VendorBoardDate[l.VendorBoard] = DateString(l.Time)
162  			data.Logs[ts][li].Reference = true
163  		}
164  	}
165  }