1package cache
  2
  3import (
  4	"fmt"
  5	"time"
  6
  7	"github.com/git-bug/git-bug/entities/bug"
  8	"github.com/git-bug/git-bug/entities/identity"
  9	"github.com/git-bug/git-bug/entity"
 10	"github.com/git-bug/git-bug/entity/dag"
 11	"github.com/git-bug/git-bug/repository"
 12)
 13
 14var ErrNoMatchingOp = fmt.Errorf("no matching operation found")
 15
 16// BugCache is a wrapper around a Bug. It provides multiple functions:
 17//
 18// 1. Provide a higher level API to use than the raw API from Bug.
 19// 2. Maintain an up-to-date Snapshot available.
 20// 3. Deal with concurrency.
 21type BugCache struct {
 22	CachedEntityBase[*bug.Snapshot, bug.Operation]
 23}
 24
 25func NewBugCache(b *bug.Bug, repo repository.ClockedRepo, getUserIdentity getUserIdentityFunc, entityUpdated func(id entity.Id) error) *BugCache {
 26	return &BugCache{
 27		CachedEntityBase: CachedEntityBase[*bug.Snapshot, bug.Operation]{
 28			repo:            repo,
 29			entityUpdated:   entityUpdated,
 30			getUserIdentity: getUserIdentity,
 31			entity:          &withSnapshot[*bug.Snapshot, bug.Operation]{Interface: b},
 32		},
 33	}
 34}
 35
 36func (c *BugCache) AddComment(message string) (entity.CombinedId, *bug.AddCommentOperation, error) {
 37	return c.AddCommentWithFiles(message, nil)
 38}
 39
 40func (c *BugCache) AddCommentWithFiles(message string, files []repository.Hash) (entity.CombinedId, *bug.AddCommentOperation, error) {
 41	author, err := c.getUserIdentity()
 42	if err != nil {
 43		return entity.UnsetCombinedId, nil, err
 44	}
 45
 46	return c.AddCommentRaw(author, time.Now().Unix(), message, files, nil)
 47}
 48
 49func (c *BugCache) AddCommentRaw(author identity.Interface, unixTime int64, message string, files []repository.Hash, metadata map[string]string) (entity.CombinedId, *bug.AddCommentOperation, error) {
 50	c.mu.Lock()
 51	commentId, op, err := bug.AddComment(c.entity, author, unixTime, message, files, metadata)
 52	c.mu.Unlock()
 53	if err != nil {
 54		return entity.UnsetCombinedId, nil, err
 55	}
 56	return commentId, op, c.notifyUpdated()
 57}
 58
 59func (c *BugCache) ChangeLabels(added []string, removed []string) ([]bug.LabelChangeResult, *bug.LabelChangeOperation, error) {
 60	author, err := c.getUserIdentity()
 61	if err != nil {
 62		return nil, nil, err
 63	}
 64
 65	return c.ChangeLabelsRaw(author, time.Now().Unix(), added, removed, nil)
 66}
 67
 68func (c *BugCache) ChangeLabelsRaw(author identity.Interface, unixTime int64, added []string, removed []string, metadata map[string]string) ([]bug.LabelChangeResult, *bug.LabelChangeOperation, error) {
 69	c.mu.Lock()
 70	changes, op, err := bug.ChangeLabels(c.entity, author, unixTime, added, removed, metadata)
 71	c.mu.Unlock()
 72	if err != nil {
 73		return changes, nil, err
 74	}
 75	return changes, op, c.notifyUpdated()
 76}
 77
 78func (c *BugCache) ForceChangeLabels(added []string, removed []string) (*bug.LabelChangeOperation, error) {
 79	author, err := c.getUserIdentity()
 80	if err != nil {
 81		return nil, err
 82	}
 83
 84	return c.ForceChangeLabelsRaw(author, time.Now().Unix(), added, removed, nil)
 85}
 86
 87func (c *BugCache) ForceChangeLabelsRaw(author identity.Interface, unixTime int64, added []string, removed []string, metadata map[string]string) (*bug.LabelChangeOperation, error) {
 88	c.mu.Lock()
 89	op, err := bug.ForceChangeLabels(c.entity, author, unixTime, added, removed, metadata)
 90	c.mu.Unlock()
 91	if err != nil {
 92		return nil, err
 93	}
 94	return op, c.notifyUpdated()
 95}
 96
 97func (c *BugCache) Open() (*bug.SetStatusOperation, error) {
 98	author, err := c.getUserIdentity()
 99	if err != nil {
100		return nil, err
101	}
102
103	return c.OpenRaw(author, time.Now().Unix(), nil)
104}
105
106func (c *BugCache) OpenRaw(author identity.Interface, unixTime int64, metadata map[string]string) (*bug.SetStatusOperation, error) {
107	c.mu.Lock()
108	op, err := bug.Open(c.entity, author, unixTime, metadata)
109	c.mu.Unlock()
110	if err != nil {
111		return nil, err
112	}
113	return op, c.notifyUpdated()
114}
115
116func (c *BugCache) Close() (*bug.SetStatusOperation, error) {
117	author, err := c.getUserIdentity()
118	if err != nil {
119		return nil, err
120	}
121
122	return c.CloseRaw(author, time.Now().Unix(), nil)
123}
124
125func (c *BugCache) CloseRaw(author identity.Interface, unixTime int64, metadata map[string]string) (*bug.SetStatusOperation, error) {
126	c.mu.Lock()
127	op, err := bug.Close(c.entity, author, unixTime, metadata)
128	c.mu.Unlock()
129	if err != nil {
130		return nil, err
131	}
132	return op, c.notifyUpdated()
133}
134
135func (c *BugCache) SetTitle(title string) (*bug.SetTitleOperation, error) {
136	author, err := c.getUserIdentity()
137	if err != nil {
138		return nil, err
139	}
140
141	return c.SetTitleRaw(author, time.Now().Unix(), title, nil)
142}
143
144func (c *BugCache) SetTitleRaw(author identity.Interface, unixTime int64, title string, metadata map[string]string) (*bug.SetTitleOperation, error) {
145	c.mu.Lock()
146	op, err := bug.SetTitle(c.entity, author, unixTime, title, metadata)
147	c.mu.Unlock()
148	if err != nil {
149		return nil, err
150	}
151	return op, c.notifyUpdated()
152}
153
154// EditCreateComment is a convenience function to edit the body of a bug (the first comment)
155func (c *BugCache) EditCreateComment(body string) (entity.CombinedId, *bug.EditCommentOperation, error) {
156	author, err := c.getUserIdentity()
157	if err != nil {
158		return entity.UnsetCombinedId, nil, err
159	}
160
161	return c.EditCreateCommentRaw(author, time.Now().Unix(), body, nil)
162}
163
164// EditCreateCommentRaw is a convenience function to edit the body of a bug (the first comment)
165func (c *BugCache) EditCreateCommentRaw(author identity.Interface, unixTime int64, body string, metadata map[string]string) (entity.CombinedId, *bug.EditCommentOperation, error) {
166	c.mu.Lock()
167	commentId, op, err := bug.EditCreateComment(c.entity, author, unixTime, body, nil, metadata)
168	c.mu.Unlock()
169	if err != nil {
170		return entity.UnsetCombinedId, nil, err
171	}
172	return commentId, op, c.notifyUpdated()
173}
174
175func (c *BugCache) EditComment(target entity.CombinedId, message string) (*bug.EditCommentOperation, error) {
176	author, err := c.getUserIdentity()
177	if err != nil {
178		return nil, err
179	}
180
181	return c.EditCommentRaw(author, time.Now().Unix(), target, message, nil)
182}
183
184func (c *BugCache) EditCommentRaw(author identity.Interface, unixTime int64, target entity.CombinedId, message string, metadata map[string]string) (*bug.EditCommentOperation, error) {
185	comment, err := c.Snapshot().SearchComment(target)
186	if err != nil {
187		return nil, err
188	}
189
190	c.mu.Lock()
191	commentId, op, err := bug.EditComment(c.entity, author, unixTime, comment.TargetId(), message, nil, metadata)
192	c.mu.Unlock()
193	if err != nil {
194		return nil, err
195	}
196	if commentId != target {
197		panic("EditComment returned unexpected comment id")
198	}
199	return op, c.notifyUpdated()
200}
201
202func (c *BugCache) SetMetadata(target entity.Id, newMetadata map[string]string) (*dag.SetMetadataOperation[*bug.Snapshot], error) {
203	author, err := c.getUserIdentity()
204	if err != nil {
205		return nil, err
206	}
207
208	return c.SetMetadataRaw(author, time.Now().Unix(), target, newMetadata)
209}
210
211func (c *BugCache) SetMetadataRaw(author identity.Interface, unixTime int64, target entity.Id, newMetadata map[string]string) (*dag.SetMetadataOperation[*bug.Snapshot], error) {
212	c.mu.Lock()
213	op, err := bug.SetMetadata(c.entity, author, unixTime, target, newMetadata)
214	c.mu.Unlock()
215	if err != nil {
216		return nil, err
217	}
218	return op, c.notifyUpdated()
219}