1package cache
2
3import (
4 "sync"
5
6 "github.com/MichaelMure/git-bug/entities/bug"
7 "github.com/MichaelMure/git-bug/entities/identity"
8 "github.com/MichaelMure/git-bug/entity"
9 "github.com/MichaelMure/git-bug/entity/dag"
10 "github.com/MichaelMure/git-bug/repository"
11)
12
13// type withSnapshot[SnapT dag.Snapshot, OpT dag.OperationWithApply[SnapT]] struct {
14// dag.Interface[SnapT, OpT]
15// snap dag.Snapshot
16// }
17//
18//
19// func (ws *withSnapshot[SnapT, OpT]) Compile() dag.Snapshot {
20// if ws.snap == nil {
21// snap := ws.Interface.Compile()
22// ws.snap = snap
23// }
24// return ws.snap
25// }
26//
27// // Append intercept Bug.Append() to update the snapshot efficiently
28// func (ws *withSnapshot[SnapT, OpT]) Append(op OpT) {
29// ws.Interface.Append(op)
30//
31// if ws.snap == nil {
32// return
33// }
34//
35// op.Apply(ws.snap)
36// ws.snap. = append(ws.snap.Operations, op)
37// }
38//
39// // Commit intercept Bug.Commit() to update the snapshot efficiently
40// func (ws *withSnapshot[SnapT, OpT]) Commit(repo repository.ClockedRepo) error {
41// err := ws.Interface.Commit(repo)
42//
43// if err != nil {
44// ws.snap = nil
45// return err
46// }
47//
48// // Commit() shouldn't change anything of the bug state apart from the
49// // initial ID set
50//
51// if ws.snap == nil {
52// return nil
53// }
54//
55// ws.snap.id = ws.Interface.Id()
56// return nil
57// }
58
59type CachedEntityBase[SnapT dag.Snapshot, OpT dag.Operation] struct {
60 entityUpdated func(id entity.Id) error
61 getUserIdentity func() (identity.Interface, error)
62 repo repository.ClockedRepo
63
64 mu sync.RWMutex
65 entity dag.Interface[SnapT, OpT]
66}
67
68func (e *CachedEntityBase[SnapT, OpT]) Id() entity.Id {
69 return e.entity.Id()
70}
71
72func (e *CachedEntityBase[SnapT, OpT]) Snapshot() SnapT {
73 e.mu.RLock()
74 defer e.mu.RUnlock()
75 return e.entity.Compile()
76}
77
78func (e *CachedEntityBase[SnapT, OpT]) notifyUpdated() error {
79 return e.entityUpdated(e.entity.Id())
80}
81
82// ResolveOperationWithMetadata will find an operation that has the matching metadata
83func (e *CachedEntityBase[SnapT, OpT]) ResolveOperationWithMetadata(key string, value string) (entity.Id, error) {
84 e.mu.RLock()
85 defer e.mu.RUnlock()
86 // preallocate but empty
87 matching := make([]entity.Id, 0, 5)
88
89 for _, op := range e.entity.Operations() {
90 opValue, ok := op.GetMetadata(key)
91 if ok && value == opValue {
92 matching = append(matching, op.Id())
93 }
94 }
95
96 if len(matching) == 0 {
97 return "", ErrNoMatchingOp
98 }
99
100 if len(matching) > 1 {
101 return "", bug.NewErrMultipleMatchOp(matching)
102 }
103
104 return matching[0], nil
105}
106
107func (e *CachedEntityBase[SnapT, OpT]) Validate() error {
108 e.mu.RLock()
109 defer e.mu.RUnlock()
110 return e.entity.Validate()
111}
112
113func (e *CachedEntityBase[SnapT, OpT]) Commit() error {
114 e.mu.Lock()
115 err := e.entity.Commit(e.repo)
116 if err != nil {
117 e.mu.Unlock()
118 return err
119 }
120 e.mu.Unlock()
121 return e.notifyUpdated()
122}
123
124func (e *CachedEntityBase[SnapT, OpT]) CommitAsNeeded() error {
125 e.mu.Lock()
126 err := e.entity.CommitAsNeeded(e.repo)
127 if err != nil {
128 e.mu.Unlock()
129 return err
130 }
131 e.mu.Unlock()
132 return e.notifyUpdated()
133}
134
135func (e *CachedEntityBase[SnapT, OpT]) NeedCommit() bool {
136 e.mu.RLock()
137 defer e.mu.RUnlock()
138 return e.entity.NeedCommit()
139}