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 defer c.mu.Unlock()
92 op, err := bug.AddCommentWithFiles(c.bug, author.Identity, unixTime, message, files)
93 if err != nil {
94 return nil, err
95 }
96
97 for key, value := range metadata {
98 op.SetMetadata(key, value)
99 }
100
101 return op, c.notifyUpdated()
102}
103
104func (c *BugCache) ChangeLabels(added []string, removed []string) ([]bug.LabelChangeResult, *bug.LabelChangeOperation, error) {
105 author, err := c.repoCache.GetUserIdentity()
106 if err != nil {
107 return nil, nil, err
108 }
109
110 return c.ChangeLabelsRaw(author, time.Now().Unix(), added, removed, nil)
111}
112
113func (c *BugCache) ChangeLabelsRaw(author *IdentityCache, unixTime int64, added []string, removed []string, metadata map[string]string) ([]bug.LabelChangeResult, *bug.LabelChangeOperation, error) {
114 c.mu.Lock()
115 defer c.mu.Unlock()
116 changes, op, err := bug.ChangeLabels(c.bug, author.Identity, unixTime, added, removed)
117 if err != nil {
118 return changes, nil, err
119 }
120
121 for key, value := range metadata {
122 op.SetMetadata(key, value)
123 }
124
125 err = c.notifyUpdated()
126 if err != nil {
127 return nil, nil, err
128 }
129
130 return changes, op, nil
131}
132
133func (c *BugCache) ForceChangeLabels(added []string, removed []string) (*bug.LabelChangeOperation, error) {
134 author, err := c.repoCache.GetUserIdentity()
135 if err != nil {
136 return nil, err
137 }
138
139 return c.ForceChangeLabelsRaw(author, time.Now().Unix(), added, removed, nil)
140}
141
142func (c *BugCache) ForceChangeLabelsRaw(author *IdentityCache, unixTime int64, added []string, removed []string, metadata map[string]string) (*bug.LabelChangeOperation, error) {
143 c.mu.Lock()
144 defer c.mu.Unlock()
145 op, err := bug.ForceChangeLabels(c.bug, author.Identity, unixTime, added, removed)
146 if err != nil {
147 return nil, err
148 }
149
150 for key, value := range metadata {
151 op.SetMetadata(key, value)
152 }
153
154 err = c.notifyUpdated()
155 if err != nil {
156 return nil, err
157 }
158
159 return op, nil
160}
161
162func (c *BugCache) Open() (*bug.SetStatusOperation, error) {
163 author, err := c.repoCache.GetUserIdentity()
164 if err != nil {
165 return nil, err
166 }
167
168 return c.OpenRaw(author, time.Now().Unix(), nil)
169}
170
171func (c *BugCache) OpenRaw(author *IdentityCache, unixTime int64, metadata map[string]string) (*bug.SetStatusOperation, error) {
172 c.mu.Lock()
173 defer c.mu.Unlock()
174 op, err := bug.Open(c.bug, author.Identity, unixTime)
175 if err != nil {
176 return nil, err
177 }
178
179 for key, value := range metadata {
180 op.SetMetadata(key, value)
181 }
182
183 return op, c.notifyUpdated()
184}
185
186func (c *BugCache) Close() (*bug.SetStatusOperation, error) {
187 author, err := c.repoCache.GetUserIdentity()
188 if err != nil {
189 return nil, err
190 }
191
192 return c.CloseRaw(author, time.Now().Unix(), nil)
193}
194
195func (c *BugCache) CloseRaw(author *IdentityCache, unixTime int64, metadata map[string]string) (*bug.SetStatusOperation, error) {
196 c.mu.Lock()
197 defer c.mu.Unlock()
198 op, err := bug.Close(c.bug, author.Identity, unixTime)
199 if err != nil {
200 return nil, err
201 }
202
203 for key, value := range metadata {
204 op.SetMetadata(key, value)
205 }
206
207 return op, c.notifyUpdated()
208}
209
210func (c *BugCache) SetTitle(title string) (*bug.SetTitleOperation, error) {
211 author, err := c.repoCache.GetUserIdentity()
212 if err != nil {
213 return nil, err
214 }
215
216 return c.SetTitleRaw(author, time.Now().Unix(), title, nil)
217}
218
219func (c *BugCache) SetTitleRaw(author *IdentityCache, unixTime int64, title string, metadata map[string]string) (*bug.SetTitleOperation, error) {
220 c.mu.Lock()
221 defer c.mu.Unlock()
222 op, err := bug.SetTitle(c.bug, author.Identity, unixTime, title)
223 if err != nil {
224 return nil, err
225 }
226
227 for key, value := range metadata {
228 op.SetMetadata(key, value)
229 }
230
231 return op, c.notifyUpdated()
232}
233
234func (c *BugCache) EditCreateComment(body string) (*bug.EditCommentOperation, error) {
235 author, err := c.repoCache.GetUserIdentity()
236 if err != nil {
237 return nil, err
238 }
239
240 return c.EditCreateCommentRaw(author, time.Now().Unix(), body, nil)
241}
242
243func (c *BugCache) EditCreateCommentRaw(author *IdentityCache, unixTime int64, body string, metadata map[string]string) (*bug.EditCommentOperation, error) {
244 c.mu.Lock()
245 defer c.mu.Unlock()
246 op, err := bug.EditCreateComment(c.bug, author.Identity, unixTime, body)
247 if err != nil {
248 return nil, err
249 }
250
251 for key, value := range metadata {
252 op.SetMetadata(key, value)
253 }
254
255 return op, c.notifyUpdated()
256}
257
258func (c *BugCache) EditComment(target entity.Id, message string) (*bug.EditCommentOperation, error) {
259 author, err := c.repoCache.GetUserIdentity()
260 if err != nil {
261 return nil, err
262 }
263
264 return c.EditCommentRaw(author, time.Now().Unix(), target, message, nil)
265}
266
267func (c *BugCache) EditCommentRaw(author *IdentityCache, unixTime int64, target entity.Id, message string, metadata map[string]string) (*bug.EditCommentOperation, error) {
268 c.mu.Lock()
269 defer c.mu.Unlock()
270 op, err := bug.EditComment(c.bug, author.Identity, unixTime, target, message)
271 if err != nil {
272 return nil, err
273 }
274
275 for key, value := range metadata {
276 op.SetMetadata(key, value)
277 }
278
279 return op, c.notifyUpdated()
280}
281
282func (c *BugCache) SetMetadata(target entity.Id, newMetadata map[string]string) (*bug.SetMetadataOperation, error) {
283 author, err := c.repoCache.GetUserIdentity()
284 if err != nil {
285 return nil, err
286 }
287
288 return c.SetMetadataRaw(author, time.Now().Unix(), target, newMetadata)
289}
290
291func (c *BugCache) SetMetadataRaw(author *IdentityCache, unixTime int64, target entity.Id, newMetadata map[string]string) (*bug.SetMetadataOperation, error) {
292 c.mu.Lock()
293 defer c.mu.Unlock()
294 op, err := bug.SetMetadata(c.bug, author.Identity, unixTime, target, newMetadata)
295 if err != nil {
296 return nil, err
297 }
298
299 return op, c.notifyUpdated()
300}
301
302func (c *BugCache) Commit() error {
303 c.mu.Lock()
304 defer c.mu.Unlock()
305 err := c.bug.Commit(c.repoCache.repo)
306 if err != nil {
307 return err
308 }
309 return c.notifyUpdated()
310}
311
312func (c *BugCache) CommitAsNeeded() error {
313 c.mu.Lock()
314 defer c.mu.Unlock()
315 err := c.bug.CommitAsNeeded(c.repoCache.repo)
316 if err != nil {
317 return err
318 }
319 return c.notifyUpdated()
320}
321
322func (c *BugCache) NeedCommit() bool {
323 c.mu.RLock()
324 defer c.mu.RUnlock()
325 return c.bug.NeedCommit()
326}