返回 DeepSeek-Reasonix
organization.go
根目录 / desktop / internal / workspacestate / organization.go
1 package workspacestate
2
3 import (
4 "bytes"
5 "context"
6 "encoding/json"
7 "errors"
8 "os"
9 "slices"
10 "strings"
11 )
12
13 // Organization is owned by the registry transaction, including source adoption.
14 // Keys are host-qualified canonical or source identities, never topic IDs.
15 type Organization struct {
16 Revision uint64 `json:"revision"`
17 ManualOrderEnabled bool `json:"manualOrderEnabled"`
18 Order []string `json:"order"`
19 Groups []OrganizationGroup `json:"groups"`
20 MigrationVersion int `json:"migrationVersion"`
21 Imported map[string]bool `json:"imported"`
22 extra map[string]json.RawMessage
23 }
24 type OrganizationGroup struct {
25 ID string `json:"id"`
26 Title string `json:"title"`
27 Members []string `json:"members"`
28 extra map[string]json.RawMessage
29 }
30
31 func (o *Organization) UnmarshalJSON(b []byte) error {
32 type plain Organization
33 var p plain
34 if err := json.Unmarshal(b, &p); err != nil {
35 return err
36 }
37 *o = Organization(p)
38 var err error
39 o.extra, err = unknownFields(b, "revision", "manualOrderEnabled", "order", "groups", "migrationVersion", "imported")
40 return err
41 }
42 func (o Organization) MarshalJSON() ([]byte, error) {
43 type plain Organization
44 b, e := json.Marshal(plain(o))
45 if e != nil {
46 return nil, e
47 }
48 return mergeUnknown(b, o.extra)
49 }
50 func (o *OrganizationGroup) UnmarshalJSON(b []byte) error {
51 type plain OrganizationGroup
52 var p plain
53 if e := json.Unmarshal(b, &p); e != nil {
54 return e
55 }
56 *o = OrganizationGroup(p)
57 var e error
58 o.extra, e = unknownFields(b, "id", "title", "members")
59 return e
60 }
61 func (o OrganizationGroup) MarshalJSON() ([]byte, error) {
62 type plain OrganizationGroup
63 b, e := json.Marshal(plain(o))
64 if e != nil {
65 return nil, e
66 }
67 return mergeUnknown(b, o.extra)
68 }
69
70 func SessionKey(id string) string { return "ref\x00local\x00" + id }
71
72 func (s *Store) PrepareCreatePresentation(ctx context.Context, id string, p Presentation) error {
73 return s.mutate(ctx, func(state *State) error {
74 if _, attached := sessionOwner(*state, id); attached {
75 return nil
76 }
77 pending, ok := state.PendingCreates[id]
78 if !ok {
79 return ErrSessionNotFound
80 }
81 if pending.Presentation == nil {
82 pending.Presentation = &p
83 state.PendingCreates[id] = pending
84 }
85 return nil
86 })
87 }
88 func normalizeOrganization(o *Organization) {
89 if o.Order == nil {
90 o.Order = []string{}
91 }
92 if o.Groups == nil {
93 o.Groups = []OrganizationGroup{}
94 }
95 if o.Imported == nil {
96 o.Imported = map[string]bool{}
97 }
98 for i := range o.Groups {
99 if o.Groups[i].Members == nil {
100 o.Groups[i].Members = []string{}
101 }
102 }
103 }
104
105 // UpdateOrganization is a workspace-scoped CAS. A nil revision is reserved for
106 // compatibility/import callers already serialized by this store's file lock.
107 func (s *Store) UpdateOrganization(ctx context.Context, id string, revision *uint64, change func(*Organization) error) (Organization, bool, error) {
108 return s.UpdateOrganizationWithState(ctx, id, revision, func(_ *State, o *Organization) error { return change(o) })
109 }
110
111 func (s *Store) UpdateOrganizationWithState(ctx context.Context, id string, revision *uint64, change func(*State, *Organization) error) (Organization, bool, error) {
112 var result Organization
113 applied := false
114 err := s.mutate(ctx, func(state *State) error {
115 w, ok := state.Workspaces[id]
116 if !ok {
117 return ErrWorkspaceNotFound
118 }
119 if w.Organization == nil {
120 w.Organization = &Organization{}
121 }
122 o := w.Organization
123 normalizeOrganization(o)
124 if revision != nil && o.Revision != *revision {
125 result = *o
126 return nil
127 }
128 before, _ := json.Marshal(o)
129 if err := change(state, o); err != nil {
130 return err
131 }
132 normalizeOrganization(o)
133 after, _ := json.Marshal(o)
134 if !bytes.Equal(before, after) {
135 o.Revision++
136 }
137 mirrorOrganizationOrder(&w)
138 state.Workspaces[id] = w
139 result = *o
140 applied = true
141 return nil
142 })
143 return result, applied, err
144 }
145
146 func mirrorOrganizationOrder(w *Workspace) {
147 if w.Organization == nil {
148 return
149 }
150 ids := []string{}
151 for _, key := range w.Organization.Order {
152 if sid, ok := strings.CutPrefix(key, "ref\x00local\x00"); ok {
153 if slices.Contains(w.SessionIDs, sid) && !slices.Contains(ids, sid) {
154 ids = append(ids, sid)
155 }
156 }
157 }
158 for _, sid := range w.SessionIDs {
159 if !slices.Contains(ids, sid) {
160 ids = append(ids, sid)
161 }
162 }
163 w.SessionIDs = ids
164 }
165
166 func attachOrganizationSession(w *Workspace, id, parentID string) {
167 if w.Organization == nil {
168 return
169 }
170 o := w.Organization
171 normalizeOrganization(o)
172 key := SessionKey(id)
173 if slices.Contains(o.Order, key) {
174 return
175 }
176 parent := SessionKey(parentID)
177 index := slices.Index(o.Order, parent)
178 if parentID != "" && index >= 0 {
179 o.Order = slices.Insert(o.Order, index+1, key)
180 } else {
181 o.Order = append(o.Order, key)
182 }
183 for i := range o.Groups {
184 if parentID != "" && slices.Contains(o.Groups[i].Members, parent) {
185 o.Groups[i].Members = append(o.Groups[i].Members, key)
186 }
187 }
188 o.Imported[key] = true
189 o.Revision++
190 }
191
192 // Replace every already-imported source alias in the same commit as its mapping.
193 func adoptOrganizationSource(state *State, m SourceMapping) {
194 w, ok := state.Workspaces[m.WorkspaceID]
195 if !ok || w.Organization == nil {
196 return
197 }
198 o := w.Organization
199 normalizeOrganization(o)
200 oldKeys := []string{"source\x00local\x00" + m.SourceKey}
201 if m.HeadID == "" {
202 oldKeys = append(oldKeys, "path\x00"+m.Path)
203 }
204 target := SessionKey(m.SessionID)
205 already := o.Imported[target]
206 changed := false
207 replace := func(values []string) []string {
208 out := []string{}
209 for _, v := range values {
210 if slices.Contains(oldKeys, v) {
211 if already {
212 continue
213 }
214 v = target
215 changed = true
216 }
217 if !slices.Contains(out, v) {
218 out = append(out, v)
219 }
220 }
221 return out
222 }
223 o.Order = replace(o.Order)
224 for i := range o.Groups {
225 o.Groups[i].Members = replace(o.Groups[i].Members)
226 }
227 for _, old := range oldKeys {
228 if o.Imported[old] {
229 o.Imported[target] = true
230 delete(o.Imported, old)
231 changed = true
232 }
233 }
234 if changed {
235 o.Revision++
236 }
237 mirrorOrganizationOrder(&w)
238 state.Workspaces[m.WorkspaceID] = w
239 }
240
241 func backupV2(path string) error {
242 b, e := os.ReadFile(path)
243 if errors.Is(e, os.ErrNotExist) {
244 return nil
245 }
246 if e != nil {
247 return e
248 }
249 var h struct {
250 Version int `json:"version"`
251 }
252 if json.Unmarshal(b, &h) != nil || h.Version != 2 {
253 return nil
254 }
255 f, e := os.OpenFile(path+".v2.bak", os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600)
256 if errors.Is(e, os.ErrExist) {
257 return nil
258 }
259 if e != nil {
260 return e
261 }
262 _, e = f.Write(b)
263 if e == nil {
264 e = f.Sync()
265 }
266 closeErr := f.Close()
267 if e != nil {
268 return e
269 }
270 return closeErr
271 }
272
272 lines GO