1package cache
2
3import (
4 "fmt"
5 "sync"
6 "time"
7
8 "github.com/MichaelMure/git-bug/bug"
9 "github.com/MichaelMure/git-bug/entity"
10 "github.com/MichaelMure/git-bug/repository"
11)
12
13var ErrNoMatchingOp = fmt.Errorf("no matching operation found")
14
15// BugCache is a wrapper around a Bug. It provide multiple functions:
16//
17// 1. Provide a higher level API to use than the raw API from Bug.
18// 2. Maintain an up to date Snapshot available.
19// 3. Deal with concurrency.
20type BugCache struct {
21 repoCache *RepoCache
22 mu sync.RWMutex
23 bug *bug.WithSnapshot
24}
25
26func NewBugCache(repoCache *RepoCache, b *bug.Bug) *BugCache {
27 return &BugCache{
28 repoCache: repoCache,
29 bug: &bug.WithSnapshot{Bug: b},
30 }
31}
32
33func (c *BugCache) Snapshot() *bug.Snapshot {
34 c.mu.RLock()
35 defer c.mu.RUnlock()
36 return c.bug.Snapshot()
37}
38
39func (c *BugCache) Id() entity.Id {
40 c.mu.RLock()
41 defer c.mu.RUnlock()
42 return c.bug.Id()
43}
44
45func (c *BugCache) notifyUpdated() error {
46 return c.repoCache.bugUpdated(c.bug.Id())
47}
48
49// ResolveOperationWithMetadata will find an operation that has the matching metadata
50func (c *BugCache) ResolveOperationWithMetadata(key string, value string) (entity.Id, error) {
51 c.mu.RLock()
52 defer c.mu.RUnlock()
53 // preallocate but empty
54 matching := make([]entity.Id, 0, 5)
55
56 it := bug.NewOperationIterator(c.bug)
57 for it.Next() {
58 op := it.Value()
59 opValue, ok := op.GetMetadata(key)
60 if ok && value == opValue {
61 matching = append(matching, op.Id())
62 }
63 }
64
65 if len(matching) == 0 {
66 return "", ErrNoMatchingOp
67 }
68
69 if len(matching) > 1 {
70 return "", bug.NewErrMultipleMatchOp(matching)
71 }
72
73 return matching[0], nil
74}
75
76func (c *BugCache) AddComment(message string) (*bug.AddCommentOperation, error) {
77 return c.AddCommentWithFiles(message, nil)
78}
79
80func (c *BugCache) AddCommentWithFiles(message string, files []repository.Hash) (*bug.AddCommentOperation, error) {
81 author, err := c.repoCache.GetUserIdentity()
82 if err != nil {
83 return nil, err
84 }
85
86 return c.AddCommentRaw(author, time.Now().Unix(), message, files, nil)
87}
88
89func (c *BugCache) AddCommentRaw(author *IdentityCache, unixTime int64, message string, files []repository.Hash, metadata map[string]string) (*bug.AddCommentOperation, error) {
90 c.mu.Lock()
91 op, err := bug.AddCommentWithFiles(c.bug, author.Identity, unixTime, message, files)
92 if err != nil {
93 c.mu.Unlock()
94 return nil, err
95 }
96
97 for key, value := range metadata {
98 op.SetMetadata(key, value)
99 }
100
101 c.mu.Unlock()
102
103 return op, c.notifyUpdated()
104}
105
106func (c *BugCache) ChangeLabels(added []string, removed []string) ([]bug.LabelChangeResult, *bug.LabelChangeOperation, error) {
107 author, err := c.repoCache.GetUserIdentity()
108 if err != nil {
109 return nil, nil, err
110 }
111
112 return c.ChangeLabelsRaw(author, time.Now().Unix(), added, removed, nil)
113}
114
115func (c *BugCache) ChangeLabelsRaw(author *IdentityCache, unixTime int64, added []string, removed []string, metadata map[string]string) ([]bug.LabelChangeResult, *bug.LabelChangeOperation, error) {
116 c.mu.Lock()
117 changes, op, err := bug.ChangeLabels(c.bug, author.Identity, unixTime, added, removed)
118 if err != nil {
119 c.mu.Unlock()
120 return changes, nil, err
121 }
122
123 for key, value := range metadata {
124 op.SetMetadata(key, value)
125 }
126
127 c.mu.Unlock()
128
129 err = c.notifyUpdated()
130 if err != nil {
131 return nil, nil, err
132 }
133
134 return changes, op, nil
135}
136
137func (c *BugCache) ForceChangeLabels(added []string, removed []string) (*bug.LabelChangeOperation, error) {
138 author, err := c.repoCache.GetUserIdentity()
139 if err != nil {
140 return nil, err
141 }
142
143 return c.ForceChangeLabelsRaw(author, time.Now().Unix(), added, removed, nil)
144}
145
146func (c *BugCache) ForceChangeLabelsRaw(author *IdentityCache, unixTime int64, added []string, removed []string, metadata map[string]string) (*bug.LabelChangeOperation, error) {
147 c.mu.Lock()
148 op, err := bug.ForceChangeLabels(c.bug, author.Identity, unixTime, added, removed)
149 if err != nil {
150 c.mu.Unlock()
151 return nil, err
152 }
153
154 for key, value := range metadata {
155 op.SetMetadata(key, value)
156 }
157
158 c.mu.Unlock()
159 err = c.notifyUpdated()
160 if err != nil {
161 return nil, err
162 }
163
164 return op, nil
165}
166
167func (c *BugCache) Open() (*bug.SetStatusOperation, error) {
168 author, err := c.repoCache.GetUserIdentity()
169 if err != nil {
170 return nil, err
171 }
172
173 return c.OpenRaw(author, time.Now().Unix(), nil)
174}
175
176func (c *BugCache) OpenRaw(author *IdentityCache, unixTime int64, metadata map[string]string) (*bug.SetStatusOperation, error) {
177 c.mu.Lock()
178 op, err := bug.Open(c.bug, author.Identity, unixTime)
179 if err != nil {
180 c.mu.Unlock()
181 return nil, err
182 }
183
184 for key, value := range metadata {
185 op.SetMetadata(key, value)
186 }
187
188 c.mu.Unlock()
189 return op, c.notifyUpdated()
190}
191
192func (c *BugCache) Close() (*bug.SetStatusOperation, error) {
193 author, err := c.repoCache.GetUserIdentity()
194 if err != nil {
195 return nil, err
196 }
197
198 return c.CloseRaw(author, time.Now().Unix(), nil)
199}
200
201func (c *BugCache) CloseRaw(author *IdentityCache, unixTime int64, metadata map[string]string) (*bug.SetStatusOperation, error) {
202 c.mu.Lock()
203 op, err := bug.Close(c.bug, author.Identity, unixTime)
204 if err != nil {
205 c.mu.Unlock()
206 return nil, err
207 }
208
209 for key, value := range metadata {
210 op.SetMetadata(key, value)
211 }
212
213 c.mu.Unlock()
214 return op, c.notifyUpdated()
215}
216
217func (c *BugCache) SetTitle(title string) (*bug.SetTitleOperation, error) {
218 author, err := c.repoCache.GetUserIdentity()
219 if err != nil {
220 return nil, err
221 }
222
223 return c.SetTitleRaw(author, time.Now().Unix(), title, nil)
224}
225
226func (c *BugCache) SetTitleRaw(author *IdentityCache, unixTime int64, title string, metadata map[string]string) (*bug.SetTitleOperation, error) {
227 c.mu.Lock()
228 op, err := bug.SetTitle(c.bug, author.Identity, unixTime, title)
229 if err != nil {
230 c.mu.Unlock()
231 return nil, err
232 }
233
234 for key, value := range metadata {
235 op.SetMetadata(key, value)
236 }
237
238 c.mu.Unlock()
239 return op, c.notifyUpdated()
240}
241
242func (c *BugCache) EditCreateComment(body string) (*bug.EditCommentOperation, error) {
243 author, err := c.repoCache.GetUserIdentity()
244 if err != nil {
245 return nil, err
246 }
247
248 return c.EditCreateCommentRaw(author, time.Now().Unix(), body, nil)
249}
250
251func (c *BugCache) EditCreateCommentRaw(author *IdentityCache, unixTime int64, body string, metadata map[string]string) (*bug.EditCommentOperation, error) {
252 c.mu.Lock()
253 op, err := bug.EditCreateComment(c.bug, author.Identity, unixTime, body)
254 if err != nil {
255 c.mu.Unlock()
256 return nil, err
257 }
258
259 for key, value := range metadata {
260 op.SetMetadata(key, value)
261 }
262
263 c.mu.Unlock()
264 return op, c.notifyUpdated()
265}
266
267func (c *BugCache) EditComment(target entity.Id, message string) (*bug.EditCommentOperation, error) {
268 author, err := c.repoCache.GetUserIdentity()
269 if err != nil {
270 return nil, err
271 }
272
273 return c.EditCommentRaw(author, time.Now().Unix(), target, message, nil)
274}
275
276func (c *BugCache) EditCommentRaw(author *IdentityCache, unixTime int64, target entity.Id, message string, metadata map[string]string) (*bug.EditCommentOperation, error) {
277 c.mu.Lock()
278 op, err := bug.EditComment(c.bug, author.Identity, unixTime, target, message)
279 if err != nil {
280 c.mu.Unlock()
281 return nil, err
282 }
283
284 for key, value := range metadata {
285 op.SetMetadata(key, value)
286 }
287
288 c.mu.Unlock()
289 return op, c.notifyUpdated()
290}
291
292func (c *BugCache) SetMetadata(target entity.Id, newMetadata map[string]string) (*bug.SetMetadataOperation, error) {
293 author, err := c.repoCache.GetUserIdentity()
294 if err != nil {
295 return nil, err
296 }
297
298 return c.SetMetadataRaw(author, time.Now().Unix(), target, newMetadata)
299}
300
301func (c *BugCache) SetMetadataRaw(author *IdentityCache, unixTime int64, target entity.Id, newMetadata map[string]string) (*bug.SetMetadataOperation, error) {
302 c.mu.Lock()
303 op, err := bug.SetMetadata(c.bug, author.Identity, unixTime, target, newMetadata)
304 if err != nil {
305 c.mu.Unlock()
306 return nil, err
307 }
308
309 c.mu.Unlock()
310 return op, c.notifyUpdated()
311}
312
313func (c *BugCache) Commit() error {
314 c.mu.Lock()
315 err := c.bug.Commit(c.repoCache.repo)
316 if err != nil {
317 c.mu.Unlock()
318 return err
319 }
320 c.mu.Unlock()
321 return c.notifyUpdated()
322}
323
324func (c *BugCache) CommitAsNeeded() error {
325 c.mu.Lock()
326 err := c.bug.CommitAsNeeded(c.repoCache.repo)
327 if err != nil {
328 c.mu.Unlock()
329 return err
330 }
331 c.mu.Unlock()
332 return c.notifyUpdated()
333}
334
335func (c *BugCache) NeedCommit() bool {
336 c.mu.RLock()
337 defer c.mu.RUnlock()
338 return c.bug.NeedCommit()
339}