]> git.r.bdr.sh - rbdr/mobius/blob - internal/mobius/threaded_news.go
bae6779effd9fbcbc8459d54bb2bd05d51ba9124
[rbdr/mobius] / internal / mobius / threaded_news.go
1 package mobius
2
3 import (
4 "cmp"
5 "encoding/binary"
6 "fmt"
7 "github.com/jhalter/mobius/hotline"
8 "gopkg.in/yaml.v3"
9 "os"
10 "slices"
11 "sort"
12 "sync"
13 )
14
15 type ThreadedNewsYAML struct {
16 ThreadedNews hotline.ThreadedNews
17
18 filePath string
19
20 mu sync.Mutex
21 }
22
23 func NewThreadedNewsYAML(filePath string) (*ThreadedNewsYAML, error) {
24 tn := &ThreadedNewsYAML{filePath: filePath}
25
26 err := tn.Load()
27
28 return tn, err
29 }
30
31 func (n *ThreadedNewsYAML) CreateGrouping(newsPath []string, name string, t [2]byte) error {
32 n.mu.Lock()
33 defer n.mu.Unlock()
34
35 cats := n.getCatByPath(newsPath)
36 cats[name] = hotline.NewsCategoryListData15{
37 Name: name,
38 Type: t,
39 Articles: map[uint32]*hotline.NewsArtData{},
40 SubCats: make(map[string]hotline.NewsCategoryListData15),
41 }
42
43 return n.writeFile()
44 }
45
46 func (n *ThreadedNewsYAML) NewsItem(newsPath []string) hotline.NewsCategoryListData15 {
47 n.mu.Lock()
48 defer n.mu.Unlock()
49
50 cats := n.ThreadedNews.Categories
51 delName := newsPath[len(newsPath)-1]
52 if len(newsPath) > 1 {
53 for _, fp := range newsPath[0 : len(newsPath)-1] {
54 cats = cats[fp].SubCats
55 }
56 }
57
58 return cats[delName]
59 }
60
61 func (n *ThreadedNewsYAML) DeleteNewsItem(newsPath []string) error {
62 n.mu.Lock()
63 defer n.mu.Unlock()
64
65 cats := n.ThreadedNews.Categories
66 delName := newsPath[len(newsPath)-1]
67 if len(newsPath) > 1 {
68 for _, fp := range newsPath[0 : len(newsPath)-1] {
69 cats = cats[fp].SubCats
70 }
71 }
72
73 delete(cats, delName)
74
75 return n.writeFile()
76 }
77
78 func (n *ThreadedNewsYAML) GetArticle(newsPath []string, articleID uint32) *hotline.NewsArtData {
79 n.mu.Lock()
80 defer n.mu.Unlock()
81
82 var cat hotline.NewsCategoryListData15
83 cats := n.ThreadedNews.Categories
84
85 for _, fp := range newsPath {
86 cat = cats[fp]
87 cats = cats[fp].SubCats
88 }
89
90 art := cat.Articles[articleID]
91 if art == nil {
92 return nil
93 }
94
95 return art
96 }
97
98 //
99 //func (n *ThreadedNewsYAML) GetNewsCatByPath(paths []string) map[string]hotline.NewsCategoryListData15 {
100 // n.mu.Lock()
101 // defer n.mu.Unlock()
102 //
103 // cats := n.getCatByPath(paths)
104 //
105 // return cats
106 //}
107
108 func (n *ThreadedNewsYAML) GetCategories(paths []string) []hotline.NewsCategoryListData15 {
109 n.mu.Lock()
110 defer n.mu.Unlock()
111
112 var categories []hotline.NewsCategoryListData15
113 for _, c := range n.getCatByPath(paths) {
114 categories = append(categories, c)
115 }
116
117 slices.SortFunc(categories, func(a, b hotline.NewsCategoryListData15) int {
118 return cmp.Compare(
119 a.Name,
120 b.Name,
121 )
122 })
123
124 return categories
125 }
126
127 func (n *ThreadedNewsYAML) getCatByPath(paths []string) map[string]hotline.NewsCategoryListData15 {
128 cats := n.ThreadedNews.Categories
129 for _, path := range paths {
130 cats = cats[path].SubCats
131 }
132
133 return cats
134 }
135
136 func (n *ThreadedNewsYAML) PostArticle(newsPath []string, parentArticleID uint32, article hotline.NewsArtData) error {
137 n.mu.Lock()
138 defer n.mu.Unlock()
139
140 binary.BigEndian.PutUint32(article.ParentArt[:], parentArticleID)
141
142 cats := n.getCatByPath(newsPath[:len(newsPath)-1])
143
144 catName := newsPath[len(newsPath)-1]
145 cat := cats[catName]
146
147 var keys []int
148 for k := range cat.Articles {
149 keys = append(keys, int(k))
150 }
151
152 nextID := uint32(1)
153 if len(keys) > 0 {
154 sort.Ints(keys)
155 prevID := uint32(keys[len(keys)-1])
156 nextID = prevID + 1
157
158 binary.BigEndian.PutUint32(article.PrevArt[:], prevID)
159
160 // Set next article Type
161 binary.BigEndian.PutUint32(cat.Articles[prevID].NextArt[:], nextID)
162 }
163
164 // Update parent article with first child reply
165 parentID := parentArticleID
166 if parentID != 0 {
167 parentArt := cat.Articles[parentID]
168
169 if parentArt.FirstChildArt == [4]byte{0, 0, 0, 0} {
170 binary.BigEndian.PutUint32(parentArt.FirstChildArt[:], nextID)
171 }
172 }
173
174 cat.Articles[nextID] = &article
175
176 cats[catName] = cat
177
178 return n.writeFile()
179 }
180
181 func (n *ThreadedNewsYAML) DeleteArticle(newsPath []string, articleID uint32, recursive bool) error {
182 n.mu.Lock()
183 defer n.mu.Unlock()
184
185 if recursive {
186 // TODO: Handle delete recursive
187 }
188
189 cats := n.getCatByPath(newsPath[:len(newsPath)-1])
190
191 catName := newsPath[len(newsPath)-1]
192
193 cat := cats[catName]
194 delete(cat.Articles, articleID)
195 cats[catName] = cat
196
197 return n.writeFile()
198 }
199
200 func (n *ThreadedNewsYAML) ListArticles(newsPath []string) hotline.NewsArtListData {
201 n.mu.Lock()
202 defer n.mu.Unlock()
203
204 var cat hotline.NewsCategoryListData15
205 cats := n.ThreadedNews.Categories
206
207 for _, fp := range newsPath {
208 cat = cats[fp]
209 cats = cats[fp].SubCats
210 }
211
212 return cat.GetNewsArtListData()
213 }
214
215 func (n *ThreadedNewsYAML) Load() error {
216 n.mu.Lock()
217 defer n.mu.Unlock()
218
219 fh, err := os.Open(n.filePath)
220 if err != nil {
221 return err
222 }
223 defer fh.Close()
224
225 n.ThreadedNews = hotline.ThreadedNews{}
226
227 decoder := yaml.NewDecoder(fh)
228 return decoder.Decode(&n.ThreadedNews)
229 }
230
231 func (n *ThreadedNewsYAML) writeFile() error {
232 out, err := yaml.Marshal(&n.ThreadedNews)
233 if err != nil {
234 return err
235 }
236
237 // Define a temporary file path in the same directory.
238 tempFilePath := n.filePath + ".tmp"
239
240 // Write the marshaled YAML to the temporary file.
241 if err := os.WriteFile(tempFilePath, out, 0644); err != nil {
242 return fmt.Errorf("write to temporary file: %v", err)
243 }
244
245 // Atomically rename the temporary file to the final file path.
246 if err := os.Rename(tempFilePath, n.filePath); err != nil {
247 return fmt.Errorf("rename temporary file to final file: %v", err)
248 }
249
250 return nil
251 }