]> git.r.bdr.sh - rbdr/mobius/blob - internal/mobius/news.go
51e212d683a0106fbda6396f5c69a0b7ef4d4c75
[rbdr/mobius] / internal / mobius / news.go
1 package mobius
2
3 import (
4 "fmt"
5 "io"
6 "os"
7 "slices"
8 "sync"
9 )
10
11 type FlatNews struct {
12 mu sync.Mutex
13
14 data []byte
15 filePath string
16
17 readOffset int // Internal offset to track read progress
18 }
19
20 func NewFlatNews(path string) (*FlatNews, error) {
21 data, err := os.ReadFile(path)
22 if err != nil {
23 return &FlatNews{}, err
24 }
25
26 return &FlatNews{
27 data: data,
28 filePath: path,
29 }, nil
30 }
31
32 func (f *FlatNews) Reload() error {
33 f.mu.Lock()
34 defer f.mu.Unlock()
35
36 data, err := os.ReadFile(f.filePath)
37 if err != nil {
38 return err
39 }
40 f.data = data
41
42 return nil
43 }
44
45 // It returns the number of bytes read and any error encountered.
46 func (f *FlatNews) Read(p []byte) (int, error) {
47 f.mu.Lock()
48 defer f.mu.Unlock()
49
50 if f.readOffset >= len(f.data) {
51 return 0, io.EOF // All bytes have been read
52 }
53
54 n := copy(p, f.data[f.readOffset:])
55
56 f.readOffset += n
57
58 return n, nil
59 }
60
61 // Write implements io.Writer for flat news.
62 // p is guaranteed to contain the full data of a news post.
63 func (f *FlatNews) Write(p []byte) (int, error) {
64 f.mu.Lock()
65 defer f.mu.Unlock()
66
67 f.data = slices.Concat(p, f.data)
68
69 tempFilePath := f.filePath + ".tmp"
70
71 if err := os.WriteFile(tempFilePath, f.data, 0644); err != nil {
72 return 0, fmt.Errorf("write to temporary file: %v", err)
73 }
74
75 // Atomically rename the temporary file to the final file path.
76 if err := os.Rename(tempFilePath, f.filePath); err != nil {
77 return 0, fmt.Errorf("rename temporary file to final file: %v", err)
78 }
79
80 return len(p), os.WriteFile(f.filePath, f.data, 0644)
81 }
82
83 func (f *FlatNews) Seek(offset int64, _ int) (int64, error) {
84 f.readOffset = int(offset)
85
86 return 0, nil
87 }