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	return c.bug.Id()
 41}
 42
 43func (c *BugCache) notifyUpdated() error {
 44	return c.repoCache.bugUpdated(c.bug.Id())
 45}
 46
 47// ResolveOperationWithMetadata will find an operation that has the matching metadata
 48func (c *BugCache) ResolveOperationWithMetadata(key string, value string) (entity.Id, error) {
 49	c.mu.RLock()
 50	defer c.mu.RUnlock()
 51	// preallocate but empty
 52	matching := make([]entity.Id, 0, 5)
 53
 54	it := bug.NewOperationIterator(c.bug)
 55	for it.Next() {
 56		op := it.Value()
 57		opValue, ok := op.GetMetadata(key)
 58		if ok && value == opValue {
 59			matching = append(matching, op.Id())
 60		}
 61	}
 62
 63	if len(matching) == 0 {
 64		return "", ErrNoMatchingOp
 65	}
 66
 67	if len(matching) > 1 {
 68		return "", bug.NewErrMultipleMatchOp(matching)
 69	}
 70
 71	return matching[0], nil
 72}
 73
 74func (c *BugCache) AddComment(message string) (*bug.AddCommentOperation, error) {
 75	return c.AddCommentWithFiles(message, nil)
 76}
 77
 78func (c *BugCache) AddCommentWithFiles(message string, files []repository.Hash) (*bug.AddCommentOperation, error) {
 79	author, err := c.repoCache.GetUserIdentity()
 80	if err != nil {
 81		return nil, err
 82	}
 83
 84	return c.AddCommentRaw(author, time.Now().Unix(), message, files, nil)
 85}
 86
 87func (c *BugCache) AddCommentRaw(author *IdentityCache, unixTime int64, message string, files []repository.Hash, metadata map[string]string) (*bug.AddCommentOperation, error) {
 88	c.mu.Lock()
 89	op, err := bug.AddCommentWithFiles(c.bug, author.Identity, unixTime, message, files)
 90	if err != nil {
 91		c.mu.Unlock()
 92		return nil, err
 93	}
 94
 95	for key, value := range metadata {
 96		op.SetMetadata(key, value)
 97	}
 98
 99	c.mu.Unlock()
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	changes, op, err := bug.ChangeLabels(c.bug, author.Identity, unixTime, added, removed)
116	if err != nil {
117		c.mu.Unlock()
118		return changes, nil, err
119	}
120
121	for key, value := range metadata {
122		op.SetMetadata(key, value)
123	}
124
125	c.mu.Unlock()
126
127	err = c.notifyUpdated()
128	if err != nil {
129		return nil, nil, err
130	}
131
132	return changes, op, nil
133}
134
135func (c *BugCache) ForceChangeLabels(added []string, removed []string) (*bug.LabelChangeOperation, error) {
136	author, err := c.repoCache.GetUserIdentity()
137	if err != nil {
138		return nil, err
139	}
140
141	return c.ForceChangeLabelsRaw(author, time.Now().Unix(), added, removed, nil)
142}
143
144func (c *BugCache) ForceChangeLabelsRaw(author *IdentityCache, unixTime int64, added []string, removed []string, metadata map[string]string) (*bug.LabelChangeOperation, error) {
145	c.mu.Lock()
146	op, err := bug.ForceChangeLabels(c.bug, author.Identity, unixTime, added, removed)
147	if err != nil {
148		c.mu.Unlock()
149		return nil, err
150	}
151
152	for key, value := range metadata {
153		op.SetMetadata(key, value)
154	}
155
156	c.mu.Unlock()
157	err = c.notifyUpdated()
158	if err != nil {
159		return nil, err
160	}
161
162	return op, nil
163}
164
165func (c *BugCache) Open() (*bug.SetStatusOperation, error) {
166	author, err := c.repoCache.GetUserIdentity()
167	if err != nil {
168		return nil, err
169	}
170
171	return c.OpenRaw(author, time.Now().Unix(), nil)
172}
173
174func (c *BugCache) OpenRaw(author *IdentityCache, unixTime int64, metadata map[string]string) (*bug.SetStatusOperation, error) {
175	c.mu.Lock()
176	op, err := bug.Open(c.bug, author.Identity, unixTime)
177	if err != nil {
178		c.mu.Unlock()
179		return nil, err
180	}
181
182	for key, value := range metadata {
183		op.SetMetadata(key, value)
184	}
185
186	c.mu.Unlock()
187	return op, c.notifyUpdated()
188}
189
190func (c *BugCache) Close() (*bug.SetStatusOperation, error) {
191	author, err := c.repoCache.GetUserIdentity()
192	if err != nil {
193		return nil, err
194	}
195
196	return c.CloseRaw(author, time.Now().Unix(), nil)
197}
198
199func (c *BugCache) CloseRaw(author *IdentityCache, unixTime int64, metadata map[string]string) (*bug.SetStatusOperation, error) {
200	c.mu.Lock()
201	op, err := bug.Close(c.bug, author.Identity, unixTime)
202	if err != nil {
203		c.mu.Unlock()
204		return nil, err
205	}
206
207	for key, value := range metadata {
208		op.SetMetadata(key, value)
209	}
210
211	c.mu.Unlock()
212	return op, c.notifyUpdated()
213}
214
215func (c *BugCache) SetTitle(title string) (*bug.SetTitleOperation, error) {
216	author, err := c.repoCache.GetUserIdentity()
217	if err != nil {
218		return nil, err
219	}
220
221	return c.SetTitleRaw(author, time.Now().Unix(), title, nil)
222}
223
224func (c *BugCache) SetTitleRaw(author *IdentityCache, unixTime int64, title string, metadata map[string]string) (*bug.SetTitleOperation, error) {
225	c.mu.Lock()
226	op, err := bug.SetTitle(c.bug, author.Identity, unixTime, title)
227	if err != nil {
228		c.mu.Unlock()
229		return nil, err
230	}
231
232	for key, value := range metadata {
233		op.SetMetadata(key, value)
234	}
235
236	c.mu.Unlock()
237	return op, c.notifyUpdated()
238}
239
240func (c *BugCache) EditCreateComment(body string) (*bug.EditCommentOperation, error) {
241	author, err := c.repoCache.GetUserIdentity()
242	if err != nil {
243		return nil, err
244	}
245
246	return c.EditCreateCommentRaw(author, time.Now().Unix(), body, nil)
247}
248
249func (c *BugCache) EditCreateCommentRaw(author *IdentityCache, unixTime int64, body string, metadata map[string]string) (*bug.EditCommentOperation, error) {
250	c.mu.Lock()
251	op, err := bug.EditCreateComment(c.bug, author.Identity, unixTime, body)
252	if err != nil {
253		c.mu.Unlock()
254		return nil, err
255	}
256
257	for key, value := range metadata {
258		op.SetMetadata(key, value)
259	}
260
261	c.mu.Unlock()
262	return op, c.notifyUpdated()
263}
264
265func (c *BugCache) EditComment(target entity.Id, message string) (*bug.EditCommentOperation, error) {
266	author, err := c.repoCache.GetUserIdentity()
267	if err != nil {
268		return nil, err
269	}
270
271	return c.EditCommentRaw(author, time.Now().Unix(), target, message, nil)
272}
273
274func (c *BugCache) EditCommentRaw(author *IdentityCache, unixTime int64, target entity.Id, message string, metadata map[string]string) (*bug.EditCommentOperation, error) {
275	c.mu.Lock()
276	op, err := bug.EditComment(c.bug, author.Identity, unixTime, target, message)
277	if err != nil {
278		c.mu.Unlock()
279		return nil, err
280	}
281
282	for key, value := range metadata {
283		op.SetMetadata(key, value)
284	}
285
286	c.mu.Unlock()
287	return op, c.notifyUpdated()
288}
289
290func (c *BugCache) SetMetadata(target entity.Id, newMetadata map[string]string) (*bug.SetMetadataOperation, error) {
291	author, err := c.repoCache.GetUserIdentity()
292	if err != nil {
293		return nil, err
294	}
295
296	return c.SetMetadataRaw(author, time.Now().Unix(), target, newMetadata)
297}
298
299func (c *BugCache) SetMetadataRaw(author *IdentityCache, unixTime int64, target entity.Id, newMetadata map[string]string) (*bug.SetMetadataOperation, error) {
300	c.mu.Lock()
301	op, err := bug.SetMetadata(c.bug, author.Identity, unixTime, target, newMetadata)
302	if err != nil {
303		c.mu.Unlock()
304		return nil, err
305	}
306
307	c.mu.Unlock()
308	return op, c.notifyUpdated()
309}
310
311func (c *BugCache) Commit() error {
312	c.mu.Lock()
313	err := c.bug.Commit(c.repoCache.repo)
314	if err != nil {
315		c.mu.Unlock()
316		return err
317	}
318	c.mu.Unlock()
319	return c.notifyUpdated()
320}
321
322func (c *BugCache) CommitAsNeeded() error {
323	c.mu.Lock()
324	err := c.bug.CommitAsNeeded(c.repoCache.repo)
325	if err != nil {
326		c.mu.Unlock()
327		return err
328	}
329	c.mu.Unlock()
330	return c.notifyUpdated()
331}
332
333func (c *BugCache) NeedCommit() bool {
334	c.mu.RLock()
335	defer c.mu.RUnlock()
336	return c.bug.NeedCommit()
337}