返回 DeepSeek-Reasonix
read_snapshot_sources.go
根目录 / desktop / read_snapshot_sources.go
1 package main
2
3 import (
4 "context"
5 "os"
6
7 "reasonix/desktop/internal/workspacestate"
8 )
9
10 // Source fences track identity/lifecycle, not file length, mtime, title or index
11 // revision. Appending content keeps a result readable; replacing/deleting its
12 // source or adopting it into another owner revokes it.
13 type readSourceFence struct {
14 app *App
15 files map[string]os.FileInfo
16 bindings map[string]string
17 versions *workspacestate.ReadVersions
18 store *readSnapshotStore
19 snapshot *readSnapshot
20 metadataOnly bool
21 indexed map[string]string
22 }
23
24 func projectionReadVersions(state workspacestate.State, supplied []*workspacestate.ReadVersions) *workspacestate.ReadVersions {
25 if len(supplied) > 0 && supplied[0] != nil {
26 return supplied[0]
27 }
28 // Callers constructing their own projection cannot borrow a different
29 // registry publication solely because its numeric generation matches.
30 return workspacestate.NewReadVersions(state)
31 }
32
33 func (a *App) newReadSourceFence(store *readSnapshotStore, snapshot *readSnapshot) (*readSourceFence, error) {
34 state, err := a.workspaceRegistry().VerifySnapshot(a.bootContext())
35 if err != nil {
36 return nil, err
37 }
38 return &readSourceFence{app: a, files: map[string]os.FileInfo{}, bindings: map[string]string{}, versions: state.ReadVersions(), store: store, snapshot: snapshot}, nil
39 }
40
41 func (f *readSourceFence) add(ctx context.Context, path string) error {
42 if path == "" {
43 return nil
44 }
45 if _, ok := f.files[path]; ok {
46 return nil
47 }
48 var info os.FileInfo
49 if !f.metadataOnly {
50 var err error
51 info, err = os.Stat(path)
52 if err != nil {
53 return err
54 }
55 } else if catalog := f.app.sessionCatalog.Load(); catalog != nil {
56 record, ok, err := catalog.GetSession(ctx, path)
57 if err != nil {
58 return err
59 }
60 if ok {
61 if f.indexed == nil {
62 f.indexed = map[string]string{}
63 }
64 f.indexed[path] = snapshotBinding("catalog-source", []any{record.Scope, record.WorkspaceRoot, record.TopicID})
65 }
66 }
67 if err := f.store.reserve(f.snapshot, int64(512+len(path)*2)); err != nil {
68 return err
69 }
70 binding, err := f.versions.Source(path)
71 if err != nil {
72 return err
73 }
74 f.files[path] = info
75 f.bindings[path] = binding
76 return nil
77 }
78
79 func (f *readSourceFence) freeze() func() error {
80 return f.validateCurrent
81 }
82
83 func (f *readSourceFence) validateCurrent() error {
84 current, err := f.app.workspaceRegistry().VerifySnapshot(f.app.bootContext())
85 if err != nil {
86 return err
87 }
88 return f.validateWithCurrent(current)
89 }
90
91 func (f *readSourceFence) validateWithCurrent(current *workspacestate.ReadSnapshot) error {
92 for path, original := range f.files {
93 if !f.metadataOnly {
94 info, err := os.Stat(path)
95 if os.IsNotExist(err) {
96 return snapshotStale("lifecycle_changed")
97 }
98 if err != nil {
99 return err
100 }
101 if !os.SameFile(original, info) {
102 return snapshotStale("lifecycle_changed")
103 }
104 } else if expected, ok := f.indexed[path]; ok {
105 catalog := f.app.sessionCatalog.Load()
106 if catalog == nil {
107 return snapshotStale("lifecycle_changed")
108 }
109 record, found, err := catalog.GetSession(f.app.bootContext(), path)
110 if err != nil {
111 return err
112 }
113 if !found || record.MissingSince != 0 || record.Health == "missing" || snapshotBinding("catalog-source", []any{record.Scope, record.WorkspaceRoot, record.TopicID}) != expected {
114 return snapshotStale("lifecycle_changed")
115 }
116 }
117 binding, err := current.ReadVersions().Source(path)
118 if err != nil {
119 return err
120 }
121 if binding != f.bindings[path] {
122 return snapshotStale("lifecycle_changed")
123 }
124 }
125 return nil
126 }
127
128 func (a *App) workspaceReadFence(versions *workspacestate.ReadVersions, workspace workspacestate.Workspace, nodes []ProjectNode) func(*workspacestate.ReadSnapshot) error {
129 states := map[string]string{}
130 for _, node := range nodes {
131 if node.Session != nil {
132 states[node.Session.SessionID] = versions.Session(node.Session.SessionID)
133 }
134 }
135 workspace.Organization = nil
136 workspace.SessionIDs = nil
137 return func(current *workspacestate.ReadSnapshot) error {
138 owner, ok := current.WorkspaceMetadata(workspace.ID)
139 if !ok || owner.Root != workspace.Root || owner.Visible != workspace.Visible {
140 return snapshotStale("lifecycle_changed")
141 }
142 for id, expected := range states {
143 member := current.Session(id)
144 if member.Workspace.ID != workspace.ID || member.OwnershipConflict || current.ReadVersions().Session(id) != expected {
145 return snapshotStale("lifecycle_changed")
146 }
147 }
148 return nil
149 }
150 }
151
151 lines GO