1package cache
  2
  3import (
  4	"bytes"
  5	"encoding/gob"
  6	"fmt"
  7
  8	"github.com/MichaelMure/git-bug/entities/identity"
  9	"github.com/MichaelMure/git-bug/entity"
 10)
 11
 12const identityCacheFile = "identity-cache"
 13
 14// identityUpdated is a callback to trigger when the excerpt of an identity
 15// changed, that is each time an identity is updated
 16func (c *RepoCache) identityUpdated(id entity.Id) error {
 17	c.muIdentity.Lock()
 18
 19	i, ok := c.identities[id]
 20	if !ok {
 21		c.muIdentity.Unlock()
 22		panic("missing identity in the cache")
 23	}
 24
 25	c.identitiesExcerpts[id] = NewIdentityExcerpt(i.Identity)
 26	c.muIdentity.Unlock()
 27
 28	// we only need to write the identity cache
 29	return c.writeIdentityCache()
 30}
 31
 32// load will try to read from the disk the identity cache file
 33func (c *RepoCache) loadIdentityCache() error {
 34	c.muIdentity.Lock()
 35	defer c.muIdentity.Unlock()
 36
 37	f, err := c.repo.LocalStorage().Open(identityCacheFile)
 38	if err != nil {
 39		return err
 40	}
 41
 42	decoder := gob.NewDecoder(f)
 43
 44	aux := struct {
 45		Version  uint
 46		Excerpts map[entity.Id]*IdentityExcerpt
 47	}{}
 48
 49	err = decoder.Decode(&aux)
 50	if err != nil {
 51		return err
 52	}
 53
 54	if aux.Version != formatVersion {
 55		return fmt.Errorf("unknown cache format version %v", aux.Version)
 56	}
 57
 58	c.identitiesExcerpts = aux.Excerpts
 59	return nil
 60}
 61
 62// write will serialize on disk the identity cache file
 63func (c *RepoCache) writeIdentityCache() error {
 64	c.muIdentity.RLock()
 65	defer c.muIdentity.RUnlock()
 66
 67	var data bytes.Buffer
 68
 69	aux := struct {
 70		Version  uint
 71		Excerpts map[entity.Id]*IdentityExcerpt
 72	}{
 73		Version:  formatVersion,
 74		Excerpts: c.identitiesExcerpts,
 75	}
 76
 77	encoder := gob.NewEncoder(&data)
 78
 79	err := encoder.Encode(aux)
 80	if err != nil {
 81		return err
 82	}
 83
 84	f, err := c.repo.LocalStorage().Create(identityCacheFile)
 85	if err != nil {
 86		return err
 87	}
 88
 89	_, err = f.Write(data.Bytes())
 90	if err != nil {
 91		return err
 92	}
 93
 94	return f.Close()
 95}
 96
 97// ResolveIdentityExcerpt retrieve a IdentityExcerpt matching the exact given id
 98func (c *RepoCache) ResolveIdentityExcerpt(id entity.Id) (*IdentityExcerpt, error) {
 99	c.muIdentity.RLock()
100	defer c.muIdentity.RUnlock()
101
102	e, ok := c.identitiesExcerpts[id]
103	if !ok {
104		return nil, identity.ErrIdentityNotExist
105	}
106
107	return e, nil
108}
109
110// ResolveIdentity retrieve an identity matching the exact given id
111func (c *RepoCache) ResolveIdentity(id entity.Id) (*IdentityCache, error) {
112	c.muIdentity.RLock()
113	cached, ok := c.identities[id]
114	c.muIdentity.RUnlock()
115	if ok {
116		return cached, nil
117	}
118
119	i, err := identity.ReadLocal(c.repo, id)
120	if err != nil {
121		return nil, err
122	}
123
124	cached = NewIdentityCache(c, i)
125
126	c.muIdentity.Lock()
127	c.identities[id] = cached
128	c.muIdentity.Unlock()
129
130	return cached, nil
131}
132
133// ResolveIdentityExcerptPrefix retrieve a IdentityExcerpt matching an id prefix.
134// It fails if multiple identities match.
135func (c *RepoCache) ResolveIdentityExcerptPrefix(prefix string) (*IdentityExcerpt, error) {
136	return c.ResolveIdentityExcerptMatcher(func(excerpt *IdentityExcerpt) bool {
137		return excerpt.Id.HasPrefix(prefix)
138	})
139}
140
141// ResolveIdentityPrefix retrieve an Identity matching an id prefix.
142// It fails if multiple identities match.
143func (c *RepoCache) ResolveIdentityPrefix(prefix string) (*IdentityCache, error) {
144	return c.ResolveIdentityMatcher(func(excerpt *IdentityExcerpt) bool {
145		return excerpt.Id.HasPrefix(prefix)
146	})
147}
148
149// ResolveIdentityImmutableMetadata retrieve an Identity that has the exact given metadata on
150// one of it's version. If multiple version have the same key, the first defined take precedence.
151func (c *RepoCache) ResolveIdentityImmutableMetadata(key string, value string) (*IdentityCache, error) {
152	return c.ResolveIdentityMatcher(func(excerpt *IdentityExcerpt) bool {
153		return excerpt.ImmutableMetadata[key] == value
154	})
155}
156
157func (c *RepoCache) ResolveIdentityExcerptMatcher(f func(*IdentityExcerpt) bool) (*IdentityExcerpt, error) {
158	id, err := c.resolveIdentityMatcher(f)
159	if err != nil {
160		return nil, err
161	}
162	return c.ResolveIdentityExcerpt(id)
163}
164
165func (c *RepoCache) ResolveIdentityMatcher(f func(*IdentityExcerpt) bool) (*IdentityCache, error) {
166	id, err := c.resolveIdentityMatcher(f)
167	if err != nil {
168		return nil, err
169	}
170	return c.ResolveIdentity(id)
171}
172
173func (c *RepoCache) resolveIdentityMatcher(f func(*IdentityExcerpt) bool) (entity.Id, error) {
174	c.muIdentity.RLock()
175	defer c.muIdentity.RUnlock()
176
177	// preallocate but empty
178	matching := make([]entity.Id, 0, 5)
179
180	for _, excerpt := range c.identitiesExcerpts {
181		if f(excerpt) {
182			matching = append(matching, excerpt.Id)
183		}
184	}
185
186	if len(matching) > 1 {
187		return entity.UnsetId, identity.NewErrMultipleMatch(matching)
188	}
189
190	if len(matching) == 0 {
191		return entity.UnsetId, identity.ErrIdentityNotExist
192	}
193
194	return matching[0], nil
195}
196
197// AllIdentityIds return all known identity ids
198func (c *RepoCache) AllIdentityIds() []entity.Id {
199	c.muIdentity.RLock()
200	defer c.muIdentity.RUnlock()
201
202	result := make([]entity.Id, len(c.identitiesExcerpts))
203
204	i := 0
205	for _, excerpt := range c.identitiesExcerpts {
206		result[i] = excerpt.Id
207		i++
208	}
209
210	return result
211}
212
213func (c *RepoCache) NewIdentityFromGitUser() (*IdentityCache, error) {
214	return c.NewIdentityFromGitUserRaw(nil)
215}
216
217func (c *RepoCache) NewIdentityFromGitUserRaw(metadata map[string]string) (*IdentityCache, error) {
218	i, err := identity.NewFromGitUser(c.repo)
219	if err != nil {
220		return nil, err
221	}
222	return c.finishIdentity(i, metadata)
223}
224
225// NewIdentity create a new identity
226// The new identity is written in the repository (commit)
227func (c *RepoCache) NewIdentity(name string, email string) (*IdentityCache, error) {
228	return c.NewIdentityRaw(name, email, "", "", nil, nil)
229}
230
231// NewIdentityFull create a new identity
232// The new identity is written in the repository (commit)
233func (c *RepoCache) NewIdentityFull(name string, email string, login string, avatarUrl string, keys []*identity.Key) (*IdentityCache, error) {
234	return c.NewIdentityRaw(name, email, login, avatarUrl, keys, nil)
235}
236
237func (c *RepoCache) NewIdentityRaw(name string, email string, login string, avatarUrl string, keys []*identity.Key, metadata map[string]string) (*IdentityCache, error) {
238	i, err := identity.NewIdentityFull(c.repo, name, email, login, avatarUrl, keys)
239	if err != nil {
240		return nil, err
241	}
242	return c.finishIdentity(i, metadata)
243}
244
245func (c *RepoCache) finishIdentity(i *identity.Identity, metadata map[string]string) (*IdentityCache, error) {
246	for key, value := range metadata {
247		i.SetMetadata(key, value)
248	}
249
250	err := i.Commit(c.repo)
251	if err != nil {
252		return nil, err
253	}
254
255	c.muIdentity.Lock()
256	if _, has := c.identities[i.Id()]; has {
257		return nil, fmt.Errorf("identity %s already exist in the cache", i.Id())
258	}
259
260	cached := NewIdentityCache(c, i)
261	c.identities[i.Id()] = cached
262	c.muIdentity.Unlock()
263
264	// force the write of the excerpt
265	err = c.identityUpdated(i.Id())
266	if err != nil {
267		return nil, err
268	}
269
270	return cached, nil
271}