1package cache
2
3import (
4 "bytes"
5 "encoding/gob"
6 "fmt"
7
8 "github.com/MichaelMure/git-bug/entity"
9 "github.com/MichaelMure/git-bug/identity"
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)
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) (*IdentityCache, error) {
234 return c.NewIdentityRaw(name, email, login, avatarUrl, nil)
235}
236
237func (c *RepoCache) NewIdentityRaw(name string, email string, login string, avatarUrl string, metadata map[string]string) (*IdentityCache, error) {
238 i := identity.NewIdentityFull(name, email, login, avatarUrl)
239 return c.finishIdentity(i, metadata)
240}
241
242func (c *RepoCache) finishIdentity(i *identity.Identity, metadata map[string]string) (*IdentityCache, error) {
243 for key, value := range metadata {
244 i.SetMetadata(key, value)
245 }
246
247 err := i.Commit(c.repo)
248 if err != nil {
249 return nil, err
250 }
251
252 c.muIdentity.Lock()
253 if _, has := c.identities[i.Id()]; has {
254 return nil, fmt.Errorf("identity %s already exist in the cache", i.Id())
255 }
256
257 cached := NewIdentityCache(c, i)
258 c.identities[i.Id()] = cached
259 c.muIdentity.Unlock()
260
261 // force the write of the excerpt
262 err = c.identityUpdated(i.Id())
263 if err != nil {
264 return nil, err
265 }
266
267 return cached, nil
268}