13 // journal appends JSON lines to one file for each day in dir, named
14 // YYYY-MM-DD.jsonl. It keeps the file of the last day open.
21 // Write appends line to the file of day and flushes it to the disk.
22 func (j *journal) Write(day string, line []byte) error {
29 j.file, err = os.OpenFile(filepath.Join(j.dir, day+".jsonl"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o600)
31 return fmt.Errorf("open day file: %w", err)
37 _, err := j.file.Write(line)
39 return fmt.Errorf("write line: %w", err)
44 return fmt.Errorf("flush line: %w", err)
50 // Close closes the open day file.
51 func (j *journal) Close() error {
60 return fmt.Errorf("close day file: %w", err)
66 // readLines decodes each line of the day files in dir, oldest day first, and
67 // gives it to add in file order.
68 func readLines[T any](ctx context.Context, dir string, add func(T) error) error {
69 paths, err := filepath.Glob(filepath.Join(dir, "*.jsonl"))
71 return fmt.Errorf("list day files: %w", err)
74 for _, path := range paths {
75 err = readFile(ctx, path, add)
84 func readFile[T any](ctx context.Context, path string, add func(T) error) (err error) {
85 file, err := os.Open(path)
87 return fmt.Errorf("open day file: %w", err)
91 err = errors.Join(err, file.Close())
94 scanner := bufio.NewScanner(file)
95 scanner.Buffer(nil, 1<<30)
96 for number := 1; scanner.Scan(); number++ {
102 err = json.Unmarshal(scanner.Bytes(), &value)
108 return fmt.Errorf("%s line %d: %w", filepath.Base(path), number, err)
114 return fmt.Errorf("read %s: %w", filepath.Base(path), err)