返回 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 members := make(map[string]bool, len(w.SessionIDs))
152 seen := make(map[string]bool, len(w.SessionIDs))
153 for _, sid := range w.SessionIDs {
154 members[sid] = true
155 }
156 for _, key := range w.Organization.Order {
157 if sid, ok := strings.CutPrefix(key, "ref\x00local\x00"); ok {
158 if members[sid] && !seen[sid] {
159 ids = append(ids, sid)
160 seen[sid] = true
161 }
162 }
163 }
164 for _, sid := range w.SessionIDs {
165 if !seen[sid] {
166 ids = append(ids, sid)
167 seen[sid] = true
168 }
169 }
170 w.SessionIDs = ids
171 }
172
173 func attachOrganizationSession(w *Workspace, id, parentID string) {
174 if w.Organization == nil {
175 return
176 }
177 o := w.Organization
178 normalizeOrganization(o)
179 key := SessionKey(id)
180 if slices.Contains(o.Order, key) {
181 return
182 }
183 parent := SessionKey(parentID)
184 index := slices.Index(o.Order, parent)
185 if parentID != "" && index >= 0 {
186 o.Order = slices.Insert(o.Order, index+1, key)
187 } else if o.ManualOrderEnabled {
188 // The sidebar pages a manual order from its head; a tail entry sits
189 // behind every older session and is out of reach once history is long.
190 o.Order = slices.Insert(o.Order, 0, key)
191 } else {
192 o.Order = append(o.Order, key)
193 }
194 for i := range o.Groups {
195 if parentID != "" && slices.Contains(o.Groups[i].Members, parent) {
196 o.Groups[i].Members = append(o.Groups[i].Members, key)
197 }
198 }
199 o.Imported[key] = true
200 o.Revision++
201 }
202
203 // Replace every already-imported source alias in the same commit as its mapping.
204 func adoptOrganizationSource(state *State, m SourceMapping) {
205 w, ok := state.Workspaces[m.WorkspaceID]
206 if !ok || w.Organization == nil {
207 return
208 }
209 o := w.Organization
210 normalizeOrganization(o)
211 oldKeys := []string{"source\x00local\x00" + m.SourceKey}
212 if m.HeadID == "" {
213 oldKeys = append(oldKeys, "path\x00"+m.Path)
214 }
215 target := SessionKey(m.SessionID)
216 already := o.Imported[target]
217 changed := false
218 replace := func(values []string) []string {
219 out := []string{}
220 for _, v := range values {
221 if slices.Contains(oldKeys, v) {
222 if already {
223 continue
224 }
225 v = target
226 changed = true
227 }
228 if !slices.Contains(out, v) {
229 out = append(out, v)
230 }
231 }
232 return out
233 }
234 o.Order = replace(o.Order)
235 for i := range o.Groups {
236 o.Groups[i].Members = replace(o.Groups[i].Members)
237 }
238 for _, old := range oldKeys {
239 if o.Imported[old] {
240 o.Imported[target] = true
241 delete(o.Imported, old)
242 changed = true
243 }
244 }
245 if changed {
246 o.Revision++
247 }
248 mirrorOrganizationOrder(&w)
249 state.Workspaces[m.WorkspaceID] = w
250 }
251
252 func backupV2(path string) error {
253 b, e := os.ReadFile(path)
254 if errors.Is(e, os.ErrNotExist) {
255 return nil
256 }
257 if e != nil {
258 return e
259 }
260 var h struct {
261 Version int `json:"version"`
262 }
263 if json.Unmarshal(b, &h) != nil || h.Version != 2 {
264 return nil
265 }
266 f, e := os.OpenFile(path+".v2.bak", os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600)
267 if errors.Is(e, os.ErrExist) {
268 return nil
269 }
270 if e != nil {
271 return e
272 }
273 _, e = f.Write(b)
274 if e == nil {
275 e = f.Sync()
276 }
277 closeErr := f.Close()
278 if e != nil {
279 return e
280 }
281 return closeErr
282 }
283
283 lines GO