Skip to main content

aster/rewind/
manager.rs

1//! Rewind 管理器
2//!
3//! 协调文件历史和对话状态的回退
4
5use super::file_history::{FileHistoryManager, RewindResult};
6use serde::{Deserialize, Serialize};
7
8/// Rewind 选项
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10pub enum RewindOption {
11    Code,
12    Conversation,
13    Both,
14    Nevermind,
15}
16
17/// 可回退的消息信息
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct RewindableMessage {
20    pub uuid: String,
21    pub index: usize,
22    pub preview: String,
23    pub timestamp: Option<i64>,
24    pub has_file_changes: bool,
25}
26
27/// Rewind 操作结果
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct RewindOperationResult {
30    pub success: bool,
31    pub option: RewindOption,
32    pub code_result: Option<RewindResult>,
33    pub conversation_result: Option<ConversationRewindResult>,
34    pub error: Option<String>,
35}
36
37/// 对话回退结果
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct ConversationRewindResult {
40    pub messages_removed: i32,
41    pub new_message_count: usize,
42}
43
44/// Rewind 管理器
45pub struct RewindManager {
46    file_history: FileHistoryManager,
47    message_count: usize,
48}
49
50impl RewindManager {
51    /// 创建新的 Rewind 管理器
52    pub fn new(session_id: impl Into<String>) -> Self {
53        Self {
54            file_history: FileHistoryManager::new(session_id),
55            message_count: 0,
56        }
57    }
58
59    /// 获取文件历史管理器
60    pub fn get_file_history_manager(&self) -> &FileHistoryManager {
61        &self.file_history
62    }
63
64    /// 获取可变文件历史管理器
65    pub fn get_file_history_manager_mut(&mut self) -> &mut FileHistoryManager {
66        &mut self.file_history
67    }
68
69    /// 记录用户消息(创建快照点)
70    pub fn record_user_message(&mut self, message_id: impl Into<String>) {
71        self.file_history.create_snapshot(message_id);
72        self.message_count += 1;
73    }
74
75    /// 记录文件修改
76    pub fn record_file_change(&mut self, file_path: impl AsRef<std::path::Path>) {
77        self.file_history
78            .backup_file_before_change(file_path.as_ref());
79        self.file_history.track_file(file_path);
80    }
81
82    /// 执行回退操作
83    pub fn rewind(&mut self, message_id: &str, option: RewindOption) -> RewindOperationResult {
84        if option == RewindOption::Nevermind {
85            return RewindOperationResult {
86                success: true,
87                option,
88                code_result: None,
89                conversation_result: None,
90                error: None,
91            };
92        }
93
94        let mut result = RewindOperationResult {
95            success: true,
96            option,
97            code_result: None,
98            conversation_result: None,
99            error: None,
100        };
101
102        // 回退代码
103        if option == RewindOption::Code || option == RewindOption::Both {
104            let code_result = self.file_history.rewind_to_message(message_id, false);
105            if !code_result.success {
106                result.success = false;
107                result.error = code_result.error.clone();
108            }
109            result.code_result = Some(code_result);
110        }
111
112        // 回退对话(简化实现,实际需要与消息存储集成)
113        if option == RewindOption::Conversation || option == RewindOption::Both {
114            result.conversation_result = Some(ConversationRewindResult {
115                messages_removed: 0,
116                new_message_count: self.message_count,
117            });
118        }
119
120        result
121    }
122
123    /// 预览回退操作
124    pub fn preview_rewind(&self, message_id: &str, option: RewindOption) -> RewindPreview {
125        let mut preview = RewindPreview::default();
126
127        if option == RewindOption::Code || option == RewindOption::Both {
128            let result = self.file_history.rewind_to_message(message_id, true);
129            preview.files_will_change = result.files_changed;
130            preview.insertions = result.insertions;
131            preview.deletions = result.deletions;
132        }
133
134        preview
135    }
136
137    /// 获取可回退的消息列表
138    pub fn get_rewindable_messages(&self) -> Vec<RewindableMessage> {
139        self.file_history
140            .get_snapshots()
141            .iter()
142            .enumerate()
143            .map(|(index, snapshot)| RewindableMessage {
144                uuid: snapshot.message_id.clone(),
145                index,
146                preview: format!("快照 #{}", index + 1),
147                timestamp: Some(snapshot.timestamp),
148                has_file_changes: !snapshot.tracked_file_backups.is_empty(),
149            })
150            .collect()
151    }
152
153    /// 获取最后一个可回退点
154    pub fn get_last_rewind_point(&self) -> Option<RewindableMessage> {
155        self.get_rewindable_messages().pop()
156    }
157
158    /// 检查是否可以回退
159    pub fn can_rewind(&self) -> bool {
160        self.file_history.get_snapshots_count() > 0
161    }
162
163    /// 清理
164    pub fn cleanup(&self) {
165        self.file_history.cleanup();
166    }
167}
168
169/// 回退预览
170#[derive(Debug, Clone, Default, Serialize, Deserialize)]
171pub struct RewindPreview {
172    pub files_will_change: Vec<String>,
173    pub messages_will_remove: usize,
174    pub insertions: u32,
175    pub deletions: u32,
176}
177
178// ============ 全局实例管理 ============
179
180use once_cell::sync::Lazy;
181use std::collections::HashMap;
182use std::sync::{Arc, RwLock};
183
184/// 全局 RewindManager 缓存
185static MANAGERS: Lazy<RwLock<HashMap<String, Arc<RwLock<RewindManager>>>>> =
186    Lazy::new(|| RwLock::new(HashMap::new()));
187
188/// 获取或创建 Rewind 管理器
189pub fn get_rewind_manager(session_id: &str) -> Arc<RwLock<RewindManager>> {
190    let mut managers = MANAGERS.write().unwrap();
191
192    if let Some(manager) = managers.get(session_id) {
193        return Arc::clone(manager);
194    }
195
196    let manager = Arc::new(RwLock::new(RewindManager::new(session_id)));
197    managers.insert(session_id.to_string(), Arc::clone(&manager));
198    manager
199}
200
201/// 清理指定会话的 Rewind 管理器
202pub fn cleanup_rewind_manager(session_id: &str) {
203    let mut managers = MANAGERS.write().unwrap();
204
205    if let Some(manager) = managers.remove(session_id) {
206        if let Ok(m) = manager.read() {
207            m.cleanup();
208        }
209    }
210}
211
212/// 清理所有 Rewind 管理器
213pub fn cleanup_all_rewind_managers() {
214    let mut managers = MANAGERS.write().unwrap();
215
216    for (_, manager) in managers.drain() {
217        if let Ok(m) = manager.read() {
218            m.cleanup();
219        }
220    }
221}
222
223// ============ 增强功能 ============
224
225impl RewindManager {
226    /// 获取会话 ID
227    pub fn session_id(&self) -> &str {
228        self.file_history.session_id()
229    }
230
231    /// 获取消息数量
232    pub fn message_count(&self) -> usize {
233        self.message_count
234    }
235
236    /// 获取被跟踪的文件数量
237    pub fn tracked_files_count(&self) -> usize {
238        self.file_history.get_tracked_files_count()
239    }
240
241    /// 获取快照数量
242    pub fn snapshots_count(&self) -> usize {
243        self.file_history.get_snapshots_count()
244    }
245
246    /// 检查是否有指定消息的快照
247    pub fn has_snapshot(&self, message_id: &str) -> bool {
248        self.file_history.has_snapshot(message_id)
249    }
250
251    /// 获取备份目录大小
252    pub fn backup_size(&self) -> u64 {
253        self.file_history.get_backup_size()
254    }
255
256    /// 批量记录文件修改
257    pub fn record_file_changes(&mut self, file_paths: &[impl AsRef<std::path::Path>]) {
258        for path in file_paths {
259            self.record_file_change(path);
260        }
261    }
262
263    /// 回退到最后一个快照点
264    pub fn rewind_to_last(&mut self, option: RewindOption) -> RewindOperationResult {
265        match self.get_last_rewind_point() {
266            Some(msg) => self.rewind(&msg.uuid, option),
267            None => RewindOperationResult {
268                success: false,
269                option,
270                code_result: None,
271                conversation_result: None,
272                error: Some("没有可回退的快照".to_string()),
273            },
274        }
275    }
276
277    /// 获取指定消息的快照详情
278    pub fn get_snapshot_details(&self, message_id: &str) -> Option<SnapshotDetails> {
279        let snapshot = self.file_history.get_snapshot(message_id)?;
280        Some(SnapshotDetails {
281            message_id: snapshot.message_id.clone(),
282            timestamp: snapshot.timestamp,
283            files_count: snapshot.tracked_file_backups.len(),
284            files: snapshot.tracked_file_backups.keys().cloned().collect(),
285        })
286    }
287}
288
289/// 快照详情
290#[derive(Debug, Clone, Serialize, Deserialize)]
291pub struct SnapshotDetails {
292    pub message_id: String,
293    pub timestamp: i64,
294    pub files_count: usize,
295    pub files: Vec<String>,
296}
297
298// ============ 单元测试 ============
299
300#[cfg(test)]
301mod tests {
302    use super::*;
303    use std::fs;
304    use std::io::Write;
305    use tempfile::TempDir;
306
307    fn create_test_file(dir: &std::path::Path, name: &str, content: &str) -> std::path::PathBuf {
308        let path = dir.join(name);
309        let mut file = fs::File::create(&path).unwrap();
310        file.write_all(content.as_bytes()).unwrap();
311        path
312    }
313
314    #[test]
315    fn test_new_manager() {
316        let manager = RewindManager::new("test-session");
317        assert_eq!(manager.session_id(), "test-session");
318        assert_eq!(manager.message_count(), 0);
319        assert!(!manager.can_rewind());
320        manager.cleanup();
321    }
322
323    #[test]
324    fn test_record_user_message() {
325        let mut manager = RewindManager::new("test-msg");
326        manager.record_user_message("msg-1");
327        assert_eq!(manager.message_count(), 1);
328        assert_eq!(manager.snapshots_count(), 1);
329        assert!(manager.has_snapshot("msg-1"));
330        assert!(manager.can_rewind());
331        manager.cleanup();
332    }
333
334    #[test]
335    fn test_record_file_change() {
336        let temp_dir = TempDir::new().unwrap();
337        let test_file = create_test_file(temp_dir.path(), "test.txt", "content");
338
339        let mut manager = RewindManager::new("test-file");
340        manager.record_file_change(&test_file);
341        assert_eq!(manager.tracked_files_count(), 1);
342        manager.cleanup();
343    }
344
345    #[test]
346    fn test_rewindable_messages() {
347        let mut manager = RewindManager::new("test-rewindable");
348        manager.record_user_message("msg-1");
349        manager.record_user_message("msg-2");
350
351        let messages = manager.get_rewindable_messages();
352        assert_eq!(messages.len(), 2);
353        assert_eq!(messages[0].uuid, "msg-1");
354        assert_eq!(messages[1].uuid, "msg-2");
355
356        let last = manager.get_last_rewind_point();
357        assert!(last.is_some());
358        assert_eq!(last.unwrap().uuid, "msg-2");
359
360        manager.cleanup();
361    }
362
363    #[test]
364    fn test_rewind_nevermind() {
365        let mut manager = RewindManager::new("test-nevermind");
366        manager.record_user_message("msg-1");
367
368        let result = manager.rewind("msg-1", RewindOption::Nevermind);
369        assert!(result.success);
370        assert_eq!(result.option, RewindOption::Nevermind);
371        assert!(result.code_result.is_none());
372
373        manager.cleanup();
374    }
375
376    #[test]
377    fn test_rewind_code() {
378        let temp_dir = TempDir::new().unwrap();
379        let test_file = create_test_file(temp_dir.path(), "test.txt", "original");
380
381        let mut manager = RewindManager::new("test-rewind-code");
382        manager.record_file_change(&test_file);
383        manager.record_user_message("msg-1");
384
385        // 修改文件
386        fs::write(&test_file, "modified").unwrap();
387
388        // 回退代码
389        let result = manager.rewind("msg-1", RewindOption::Code);
390        assert!(result.success);
391        assert!(result.code_result.is_some());
392
393        // 验证文件恢复
394        let content = fs::read_to_string(&test_file).unwrap();
395        assert_eq!(content, "original");
396
397        manager.cleanup();
398    }
399
400    #[test]
401    fn test_preview_rewind() {
402        let temp_dir = TempDir::new().unwrap();
403        let test_file = create_test_file(temp_dir.path(), "test.txt", "line1\nline2\n");
404
405        let mut manager = RewindManager::new("test-preview");
406        manager.record_file_change(&test_file);
407        manager.record_user_message("msg-1");
408
409        // 修改文件
410        fs::write(&test_file, "line1\nline2\nline3\n").unwrap();
411
412        let preview = manager.preview_rewind("msg-1", RewindOption::Code);
413        assert!(!preview.files_will_change.is_empty());
414
415        // 文件应该没有变化(预览模式)
416        let content = fs::read_to_string(&test_file).unwrap();
417        assert_eq!(content, "line1\nline2\nline3\n");
418
419        manager.cleanup();
420    }
421
422    #[test]
423    fn test_rewind_to_last() {
424        let mut manager = RewindManager::new("test-last");
425
426        // 没有快照时回退
427        let result = manager.rewind_to_last(RewindOption::Code);
428        assert!(!result.success);
429
430        // 有快照时回退
431        manager.record_user_message("msg-1");
432        let result = manager.rewind_to_last(RewindOption::Code);
433        assert!(result.success);
434
435        manager.cleanup();
436    }
437
438    #[test]
439    fn test_snapshot_details() {
440        let temp_dir = TempDir::new().unwrap();
441        let test_file = create_test_file(temp_dir.path(), "test.txt", "content");
442
443        let mut manager = RewindManager::new("test-details");
444        manager.record_file_change(&test_file);
445        manager.record_user_message("msg-1");
446
447        let details = manager.get_snapshot_details("msg-1");
448        assert!(details.is_some());
449        let details = details.unwrap();
450        assert_eq!(details.message_id, "msg-1");
451        assert_eq!(details.files_count, 1);
452
453        manager.cleanup();
454    }
455
456    #[test]
457    fn test_global_manager() {
458        let manager1 = get_rewind_manager("global-test");
459        let manager2 = get_rewind_manager("global-test");
460
461        // 应该是同一个实例
462        assert!(Arc::ptr_eq(&manager1, &manager2));
463
464        cleanup_rewind_manager("global-test");
465    }
466
467    #[test]
468    fn test_batch_file_changes() {
469        let temp_dir = TempDir::new().unwrap();
470        let file1 = create_test_file(temp_dir.path(), "a.txt", "a");
471        let file2 = create_test_file(temp_dir.path(), "b.txt", "b");
472
473        let mut manager = RewindManager::new("test-batch");
474        manager.record_file_changes(&[&file1, &file2]);
475        assert_eq!(manager.tracked_files_count(), 2);
476
477        manager.cleanup();
478    }
479}