返回 DeepSeek-Reasonix
topic_archive_metadata.go
根目录 / desktop / topic_archive_metadata.go
1 package main
2
3 import (
4 "crypto/sha256"
5 "encoding/hex"
6 "encoding/json"
7 "errors"
8 "fmt"
9 "os"
10 "path/filepath"
11 "strings"
12 "time"
13
14 "reasonix/internal/agent"
15 "reasonix/internal/fileutil"
16 )
17
18 const topicArchiveMetadataPendingDir = "desktop-topic-archive-pending"
19
20 type topicArchiveMetadataPending struct {
21 TopicID string `json:"topicId"`
22 CreatedAt int64 `json:"createdAt"`
23 Sessions []topicArchiveMetadataPendingSession `json:"sessions,omitempty"`
24 }
25
26 type topicArchiveMetadataPendingSession struct {
27 Dir string `json:"dir"`
28 SessionPath string `json:"sessionPath"`
29 }
30
31 func topicArchiveMetadataPendingPath(topicID string) string {
32 digest := sha256.Sum256([]byte(strings.TrimSpace(topicID)))
33 return filepath.Join(desktopConfigDir(), topicArchiveMetadataPendingDir, hex.EncodeToString(digest[:])+".json")
34 }
35
36 func markTopicArchiveMetadataPending(topicID string, targets []topicTrashTarget) error {
37 topicID = strings.TrimSpace(topicID)
38 if topicID == "" {
39 return fmt.Errorf("topicID is required")
40 }
41 path := topicArchiveMetadataPendingPath(topicID)
42 if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
43 return err
44 }
45 sessions := make([]topicArchiveMetadataPendingSession, 0, len(targets))
46 for _, target := range targets {
47 sessions = append(sessions, topicArchiveMetadataPendingSession{Dir: target.dir, SessionPath: target.sessionPath})
48 }
49 body, err := json.MarshalIndent(topicArchiveMetadataPending{
50 TopicID: topicID, CreatedAt: time.Now().UnixMilli(), Sessions: sessions,
51 }, "", " ")
52 if err != nil {
53 return err
54 }
55 return fileutil.AtomicWriteFile(path, body, 0o644)
56 }
57
58 func clearTopicArchiveMetadataPending(topicID string) error {
59 if err := os.Remove(topicArchiveMetadataPendingPath(topicID)); err != nil && !os.IsNotExist(err) {
60 return err
61 }
62 return nil
63 }
64
65 func listTopicArchiveMetadataPending() ([]topicArchiveMetadataPending, error) {
66 dir := filepath.Join(desktopConfigDir(), topicArchiveMetadataPendingDir)
67 entries, err := os.ReadDir(dir)
68 if err != nil {
69 if os.IsNotExist(err) {
70 return nil, nil
71 }
72 return nil, err
73 }
74 pending := make([]topicArchiveMetadataPending, 0, len(entries))
75 for _, entry := range entries {
76 if entry.IsDir() || filepath.Ext(entry.Name()) != ".json" {
77 continue
78 }
79 body, err := os.ReadFile(filepath.Join(dir, entry.Name()))
80 if err != nil {
81 return nil, err
82 }
83 var item topicArchiveMetadataPending
84 if err := json.Unmarshal(body, &item); err != nil {
85 return nil, err
86 }
87 item.TopicID = strings.TrimSpace(item.TopicID)
88 if item.TopicID == "" || filepath.Base(topicArchiveMetadataPendingPath(item.TopicID)) != entry.Name() {
89 return nil, fmt.Errorf("invalid topic archive metadata marker")
90 }
91 pending = append(pending, item)
92 }
93 return pending, nil
94 }
95
96 func reconcileTopicArchiveMetadataPending(deleteTopic func(string) error) error {
97 if deleteTopic == nil {
98 return fmt.Errorf("topic archive metadata reconciler is unavailable")
99 }
100 pending, err := listTopicArchiveMetadataPending()
101 if err != nil {
102 return err
103 }
104 var errs []error
105 for _, item := range pending {
106 itemFailed := false
107 for _, target := range item.Sessions {
108 sessionPath, key, err := validateSessionPath(target.Dir, target.SessionPath)
109 if err == nil {
110 err = agent.MarkCleanupPending(sessionPath, "delete")
111 }
112 if err == nil {
113 err = reconcileDesktopTrashSessionArtifacts(target.Dir, sessionPath, key)
114 }
115 if err != nil {
116 errs = append(errs, err)
117 itemFailed = true
118 }
119 }
120 if err := deleteTopic(item.TopicID); err != nil {
121 errs = append(errs, err)
122 itemFailed = true
123 }
124 if itemFailed {
125 continue
126 }
127 if err := clearTopicArchiveMetadataPending(item.TopicID); err != nil {
128 errs = append(errs, err)
129 }
130 }
131 return errors.Join(errs...)
132 }
133
133 lines GO