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 with one write to the operating system,
22 // without a buffer. A crash of the process then loses nothing.
23 func (j *journal) Write(day string, line []byte) error {
30 j.file, err = os.OpenFile(filepath.Join(j.dir, day+".jsonl"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o600)
32 return fmt.Errorf("open day file: %w", err)
38 _, err := j.file.Write(line)
40 return fmt.Errorf("write line: %w", err)
46 // Close closes the open day file.
47 func (j *journal) Close() error {
56 return fmt.Errorf("close day file: %w", err)
62 // readLines decodes each line of the day files in dir, oldest day first, and
63 // gives it to add in file order.
64 func readLines[T any](ctx context.Context, dir string, add func(T) error) error {
65 paths, err := filepath.Glob(filepath.Join(dir, "*.jsonl"))
67 return fmt.Errorf("list day files: %w", err)
70 for _, path := range paths {
71 err = readFile(ctx, path, add)
80 func readFile[T any](ctx context.Context, path string, add func(T) error) (err error) {
81 file, err := os.Open(path)
83 return fmt.Errorf("open day file: %w", err)
87 err = errors.Join(err, file.Close())
90 scanner := bufio.NewScanner(file)
91 scanner.Buffer(nil, 1<<30)
92 for number := 1; scanner.Scan(); number++ {
98 err = json.Unmarshal(scanner.Bytes(), &value)
104 return fmt.Errorf("%s line %d: %w", filepath.Base(path), number, err)
110 return fmt.Errorf("read %s: %w", filepath.Base(path), err)