返回 DeepSeek-Reasonix
topic_removal_legacy.go
根目录 / desktop / topic_removal_legacy.go
1 package main
2
3 import (
4 "os"
5 "slices"
6
7 "reasonix/desktop/internal/legacycleanup"
8 "reasonix/desktop/internal/workspacestate"
9 )
10
11 // Old installations may have chat files but no indexed topic metadata. Their
12 // persisted source ownership is sufficient for archive, never for discard.
13 func (a *App) inspectLegacyTopicRemoval(state workspacestate.State, target TopicRemovalTarget) (TopicRemovalInspection, error) {
14 out := TopicRemovalInspection{Target: target, Disposition: "archive_sessions", Allowed: true}
15 owner := ""
16 sources := map[string]any{}
17 add := func(match topicSessionMatch) error {
18 wid := topicRemovalWorkspaceID(state, match.scope, match.workspaceRoot)
19 if (owner != "" && owner != wid) || (target.WorkspaceID != "" && target.WorkspaceID != wid) {
20 return workspacestate.ErrMutationConflict
21 }
22 owner = wid
23 info, err := os.Stat(match.path)
24 if err != nil {
25 return err
26 }
27 sources[match.path] = []any{match.scope, match.workspaceRoot, info.Size(), info.ModTime().UnixNano()}
28 return nil
29 }
30 for _, dir := range a.knownSessionDirs() {
31 index, err := topicSessionIndexForDir(dir)
32 if err != nil {
33 return out, err
34 }
35 for _, match := range index.byTopic[target.TopicID] {
36 if err := add(match); err != nil {
37 return out, err
38 }
39 }
40 }
41 a.mu.RLock()
42 var runtimeSources []topicSessionMatch
43 for _, tab := range a.runtimeTabsLocked() {
44 if tab != nil && tab.TopicID == target.TopicID {
45 if path := canonicalTabSessionPath(tab.currentSessionPath()); path != "" {
46 runtimeSources = append(runtimeSources, topicSessionMatch{path: path, scope: tab.Scope, workspaceRoot: tab.WorkspaceRoot})
47 }
48 }
49 }
50 a.mu.RUnlock()
51 for _, match := range runtimeSources {
52 if _, found := sources[match.path]; !found {
53 if err := add(match); err != nil {
54 return out, err
55 }
56 }
57 }
58 if owner == "" {
59 return out, workspacestate.ErrMutationConflict
60 }
61 out.Target.WorkspaceID = owner
62 out.Token = topicRemovalToken(legacycleanup.Candidate{WorkspaceID: owner, TopicID: target.TopicID}, sources)
63 if a.topicHasActiveRuntimeWork(target.TopicID) {
64 out.Allowed, out.Reason = false, "busy"
65 }
66 return out, nil
67 }
68
69 func (a *App) validateCompatibleTopicOwner(state workspacestate.State, topicID string, hasSources bool) error {
70 file, err := readTopicRemovalProjects()
71 if err != nil {
72 return err
73 }
74 owners := map[string]bool{}
75 if slices.Contains(file.GlobalTopics, topicID) {
76 owners[topicRemovalWorkspaceID(state, "global", "")] = true
77 }
78 for _, project := range file.Projects {
79 if slices.Contains(project.Topics, topicID) {
80 owners[topicRemovalWorkspaceID(state, "project", project.Root)] = true
81 }
82 }
83 canonical, found, err := a.inspectCanonicalTopicRemoval(state, TopicRemovalTarget{TopicID: topicID})
84 if err != nil {
85 return err
86 }
87 if found {
88 owners[canonical.Target.WorkspaceID] = true
89 }
90 if hasSources {
91 legacy, err := a.inspectLegacyTopicRemoval(state, TopicRemovalTarget{TopicID: topicID})
92 if err != nil {
93 return err
94 }
95 owners[legacy.Target.WorkspaceID] = true
96 }
97 if len(owners) > 1 {
98 return workspacestate.ErrMutationConflict
99 }
100 return nil
101 }
102
102 lines GO