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