-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsession.go
More file actions
131 lines (119 loc) · 4.12 KB
/
Copy pathsession.go
File metadata and controls
131 lines (119 loc) · 4.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package qi
import (
"sync"
"github.com/iamseth/yin"
)
// Session tracks generation-scoped per-peer cursors around a caller-supplied
// JSONFile. Its mutex serializes Session operations and validates identity before
// yin ingest, but callers retain the file and must not mutate it concurrently.
// Store is the preferred owning, concurrency-safe end-to-end boundary.
type Session struct {
mu sync.Mutex
identity DocumentIdentity
file *JSONFile
cursors map[yin.ReplicaID]Cursor
}
// NewSession wraps file for the default document generation. It does not copy or
// take exclusive ownership of file; the caller must coordinate all direct use.
// NewSession panics if file is nil.
func NewSession(file *JSONFile) *Session {
return NewSessionForDocument(file, DocumentIdentity{DocumentID: DefaultDocumentID, Generation: InitialGeneration})
}
// NewSessionForDocument wraps file at an explicit document boundary. It does
// not copy or take exclusive ownership of file; the caller must coordinate all
// direct use. It panics if file is nil or identity is invalid.
func NewSessionForDocument(file *JSONFile, identity DocumentIdentity) *Session {
if file == nil {
panic("qi: nil JSONFile")
}
if err := validateIdentity(identity); err != nil {
panic(err)
}
return &Session{identity: identity, file: file, cursors: make(map[yin.ReplicaID]Cursor)}
}
// Cursor returns a copy of the generation-scoped cursor recorded for peer.
func (s *Session) Cursor(peer yin.ReplicaID) Cursor {
s.mu.Lock()
defer s.mu.Unlock()
cursor, ok := s.cursors[peer]
if !ok {
return Cursor{Identity: s.identity}
}
cursor.Version = cursor.Version.Clone()
return cursor
}
// SetCursor replaces a peer cursor after validating its identity.
func (s *Session) SetCursor(peer yin.ReplicaID, cursor Cursor) error {
s.mu.Lock()
defer s.mu.Unlock()
if err := compareIdentity(s.identity, cursor.Identity); err != nil {
return err
}
cursor.Version = cursor.Version.Clone()
s.cursors[peer] = cursor
return nil
}
// Mark joins version into a peer's current-generation cursor.
func (s *Session) Mark(peer yin.ReplicaID, cursor Cursor) error {
s.mu.Lock()
defer s.mu.Unlock()
if err := compareIdentity(s.identity, cursor.Identity); err != nil {
return err
}
current := s.cursors[peer]
current.Identity = s.identity
current.Version = current.Version.Join(cursor.Version)
s.cursors[peer] = current
return nil
}
// ExtractDeltaFor returns changes beyond a peer's generation-scoped cursor
// without advancing it. Call Mark or SetCursor only after the caller's protocol
// has established the corresponding peer coverage.
func (s *Session) ExtractDeltaFor(peer yin.ReplicaID) (DeltaArtifact, bool, error) {
s.mu.Lock()
defer s.mu.Unlock()
cursor, ok := s.cursors[peer]
if !ok {
cursor = Cursor{Identity: s.identity}
}
return s.extractLocked(cursor)
}
// ExtractDeltaSince returns changes beyond cursor without advancing it.
func (s *Session) ExtractDeltaSince(cursor Cursor) (DeltaArtifact, bool, error) {
s.mu.Lock()
defer s.mu.Unlock()
return s.extractLocked(cursor)
}
// ApplyDeltaFrom rejects a mismatched artifact before decoding or applying yin
// and marks the sender cursor only after successful decoding.
func (s *Session) ApplyDeltaFrom(peer yin.ReplicaID, artifact DeltaArtifact) (bool, error) {
s.mu.Lock()
defer s.mu.Unlock()
if err := compareIdentity(s.identity, artifact.Identity); err != nil {
return false, err
}
delta, err := decodeDelta(artifact.Payload)
if err != nil {
return false, err
}
changed := s.file.doc.ApplyDelta(delta)
current := s.cursors[peer]
current.Identity = s.identity
current.Version = current.Version.Join(delta.Version())
s.cursors[peer] = current
return changed, nil
}
func (s *Session) extractLocked(cursor Cursor) (DeltaArtifact, bool, error) {
if err := compareIdentity(s.identity, cursor.Identity); err != nil {
return DeltaArtifact{}, false, err
}
delta := s.file.doc.ExtractDelta(cursor.Version.Clone())
if delta == nil {
return DeltaArtifact{}, false, nil
}
data, err := encodeDelta(delta)
if err != nil {
return DeltaArtifact{}, false, err
}
return DeltaArtifact{Identity: s.identity, Payload: data}, true, nil
}