返回 DeepSeek-Reasonix
session_purged_topics.go
根目录 / desktop / session_purged_topics.go
1 package main
2
3 import (
4 "encoding/hex"
5 "strings"
6
7 "reasonix/desktop/internal/workspacestate"
8 )
9
10 func (a *App) validatePlaceholderTopicOpen(topicID, sessionPath string) error {
11 if strings.TrimSpace(sessionPath) != "" || strings.TrimSpace(topicID) == "" {
12 return nil
13 }
14 state, err := a.workspaceRegistry().Load(a.bootContext())
15 if err != nil {
16 return err
17 }
18 if purgedCanonicalTopicIDs(state)[topicID] {
19 return newSessionOperationError(sessionOperationTargetNotFound, "This session was permanently deleted.")
20 }
21 return nil
22 }
23
24 // Deleted sessions still own their old metadata topics. A topic with another
25 // surviving session/reservation is shared and must remain available to it.
26 func purgedCanonicalTopicIDs(state workspacestate.State) map[string]bool {
27 retired := map[string]bool{}
28 for _, op := range state.PendingOperations {
29 if (op.Kind != "purge" && op.Phase != "committed") || op.Presentation == nil || op.Presentation.TopicID == "" || len(op.SessionIDs) != 1 {
30 continue
31 }
32 if state.SessionStates[op.SessionIDs[0]].Lifecycle == workspacestate.Deleted {
33 retired[op.Presentation.TopicID] = true
34 }
35 }
36 for id, status := range state.SessionStates {
37 if status.Lifecycle != workspacestate.Deleted {
38 continue
39 }
40 if topic := state.Presentation[id].TopicID; topic != "" {
41 retired[topic] = true
42 }
43 // Previous manual-creation writers removed presentation on purge.
44 // Their session and topic IDs share this exact persisted hash; this
45 // recovers ownership without guessing from titles or deleting files.
46 if suffix, ok := strings.CutPrefix(id, "desktop-manual-"); ok && len(suffix) == 32 {
47 if _, err := hex.DecodeString(suffix); err == nil {
48 retired["manual-"+suffix] = true
49 }
50 }
51 }
52 for id, presentation := range state.Presentation {
53 if state.SessionStates[id].Lifecycle != workspacestate.Deleted {
54 delete(retired, presentation.TopicID)
55 }
56 }
57 for _, pending := range state.PendingCreates {
58 if pending.Presentation != nil {
59 delete(retired, pending.Presentation.TopicID)
60 }
61 }
62 return retired
63 }
64
64 lines GO