lazy_bug.go

  1package models
  2
  3import (
  4	"sync"
  5	"time"
  6
  7	"github.com/git-bug/git-bug/cache"
  8	"github.com/git-bug/git-bug/entities/bug"
  9	"github.com/git-bug/git-bug/entities/common"
 10	"github.com/git-bug/git-bug/entity"
 11	"github.com/git-bug/git-bug/entity/dag"
 12)
 13
 14// BugWrapper is an interface used by the GraphQL resolvers to handle a bug.
 15// Depending on the situation, a Bug can already be fully loaded in memory or not.
 16// This interface is used to wrap either a lazyBug or a loadedBug depending on the situation.
 17type BugWrapper interface {
 18	Id() entity.Id
 19	LastEdit() time.Time
 20	Status() common.Status
 21	Title() string
 22	Comments() ([]bug.Comment, error)
 23	Labels() []common.Label
 24	Author() (IdentityWrapper, error)
 25	Actors() ([]IdentityWrapper, error)
 26	Participants() ([]IdentityWrapper, error)
 27	CreatedAt() time.Time
 28	Timeline() ([]bug.TimelineItem, error)
 29	Operations() ([]dag.Operation, error)
 30
 31	// IsAuthored is a sign-post method for gqlgen, to mark compliance to an interface.
 32	IsAuthored()
 33	// IsEntity is a sign post-method for gqlgen, to mark compliance to an interface.
 34	IsEntity()
 35}
 36
 37var _ BugWrapper = &lazyBug{}
 38
 39// lazyBug is a lazy-loading wrapper that fetches data from the cache (BugExcerpt) in priority,
 40// and load the complete bug and snapshot only when necessary.
 41type lazyBug struct {
 42	cache   *cache.RepoCache
 43	excerpt *cache.BugExcerpt
 44
 45	mu   sync.Mutex
 46	snap *bug.Snapshot
 47}
 48
 49func NewLazyBug(cache *cache.RepoCache, excerpt *cache.BugExcerpt) *lazyBug {
 50	return &lazyBug{
 51		cache:   cache,
 52		excerpt: excerpt,
 53	}
 54}
 55
 56func (lb *lazyBug) load() error {
 57	lb.mu.Lock()
 58	defer lb.mu.Unlock()
 59
 60	if lb.snap != nil {
 61		return nil
 62	}
 63
 64	b, err := lb.cache.Bugs().Resolve(lb.excerpt.Id())
 65	if err != nil {
 66		return err
 67	}
 68
 69	lb.snap = b.Snapshot()
 70	return nil
 71}
 72
 73func (lb *lazyBug) identity(id entity.Id) (IdentityWrapper, error) {
 74	i, err := lb.cache.Identities().ResolveExcerpt(id)
 75	if err != nil {
 76		return nil, err
 77	}
 78	return &lazyIdentity{cache: lb.cache, excerpt: i}, nil
 79}
 80
 81func (lb *lazyBug) Id() entity.Id {
 82	return lb.excerpt.Id()
 83}
 84
 85func (lb *lazyBug) LastEdit() time.Time {
 86	return lb.excerpt.EditTime()
 87}
 88
 89func (lb *lazyBug) Status() common.Status {
 90	return lb.excerpt.Status
 91}
 92
 93func (lb *lazyBug) Title() string {
 94	return lb.excerpt.Title
 95}
 96
 97func (lb *lazyBug) Comments() ([]bug.Comment, error) {
 98	err := lb.load()
 99	if err != nil {
100		return nil, err
101	}
102	return lb.snap.Comments, nil
103}
104
105func (lb *lazyBug) Labels() []common.Label {
106	return lb.excerpt.Labels
107}
108
109func (lb *lazyBug) Author() (IdentityWrapper, error) {
110	return lb.identity(lb.excerpt.AuthorId)
111}
112
113func (lb *lazyBug) Actors() ([]IdentityWrapper, error) {
114	result := make([]IdentityWrapper, len(lb.excerpt.Actors))
115	for i, actorId := range lb.excerpt.Actors {
116		actor, err := lb.identity(actorId)
117		if err != nil {
118			return nil, err
119		}
120		result[i] = actor
121	}
122	return result, nil
123}
124
125func (lb *lazyBug) Participants() ([]IdentityWrapper, error) {
126	result := make([]IdentityWrapper, len(lb.excerpt.Participants))
127	for i, participantId := range lb.excerpt.Participants {
128		participant, err := lb.identity(participantId)
129		if err != nil {
130			return nil, err
131		}
132		result[i] = participant
133	}
134	return result, nil
135}
136
137func (lb *lazyBug) CreatedAt() time.Time {
138	return lb.excerpt.CreateTime()
139}
140
141func (lb *lazyBug) Timeline() ([]bug.TimelineItem, error) {
142	err := lb.load()
143	if err != nil {
144		return nil, err
145	}
146	return lb.snap.Timeline, nil
147}
148
149func (lb *lazyBug) Operations() ([]dag.Operation, error) {
150	err := lb.load()
151	if err != nil {
152		return nil, err
153	}
154	return lb.snap.Operations, nil
155}
156
157// IsAuthored is a sign-post method for gqlgen, to mark compliance to an interface.
158func (lb *lazyBug) IsAuthored() {}
159
160// IsEntity is a sign post-method for gqlgen, to mark compliance to an interface.
161func (lb *lazyBug) IsEntity() {}
162
163var _ BugWrapper = &loadedBug{}
164
165type loadedBug struct {
166	*bug.Snapshot
167}
168
169func NewLoadedBug(snap *bug.Snapshot) *loadedBug {
170	return &loadedBug{Snapshot: snap}
171}
172
173func (l *loadedBug) LastEdit() time.Time {
174	return l.Snapshot.EditTime()
175}
176
177func (l *loadedBug) Status() common.Status {
178	return l.Snapshot.Status
179}
180
181func (l *loadedBug) Title() string {
182	return l.Snapshot.Title
183}
184
185func (l *loadedBug) Comments() ([]bug.Comment, error) {
186	return l.Snapshot.Comments, nil
187}
188
189func (l *loadedBug) Labels() []common.Label {
190	return l.Snapshot.Labels
191}
192
193func (l *loadedBug) Author() (IdentityWrapper, error) {
194	return NewLoadedIdentity(l.Snapshot.Author), nil
195}
196
197func (l *loadedBug) Actors() ([]IdentityWrapper, error) {
198	res := make([]IdentityWrapper, len(l.Snapshot.Actors))
199	for i, actor := range l.Snapshot.Actors {
200		res[i] = NewLoadedIdentity(actor)
201	}
202	return res, nil
203}
204
205func (l *loadedBug) Participants() ([]IdentityWrapper, error) {
206	res := make([]IdentityWrapper, len(l.Snapshot.Participants))
207	for i, participant := range l.Snapshot.Participants {
208		res[i] = NewLoadedIdentity(participant)
209	}
210	return res, nil
211}
212
213func (l *loadedBug) CreatedAt() time.Time {
214	return l.Snapshot.CreateTime
215}
216
217func (l *loadedBug) Timeline() ([]bug.TimelineItem, error) {
218	return l.Snapshot.Timeline, nil
219}
220
221func (l *loadedBug) Operations() ([]dag.Operation, error) {
222	return l.Snapshot.Operations, nil
223}
224
225// IsAuthored is a sign-post method for gqlgen, to mark compliance to an interface.
226func (l *loadedBug) IsAuthored() {}
227
228// IsEntity is a sign post-method for gqlgen, to mark compliance to an interface.
229func (l *loadedBug) IsEntity() {}