Skip to main content

aria2_core/session/
save_session_command.rs

1use std::path::PathBuf;
2use std::sync::Arc;
3use tokio::sync::RwLock;
4
5use async_trait::async_trait;
6use tracing::{debug, info};
7
8use super::session_serializer;
9use crate::engine::command::{Command, CommandStatus};
10use crate::error::Result;
11use crate::request::request_group_man::RequestGroupMan;
12
13pub struct SaveSessionCommand {
14    path: PathBuf,
15    request_group_man: Arc<RwLock<RequestGroupMan>>,
16    status: CommandStatus,
17}
18
19impl SaveSessionCommand {
20    pub fn new(path: PathBuf, man: Arc<RwLock<RequestGroupMan>>) -> Self {
21        SaveSessionCommand {
22            path,
23            request_group_man: man,
24            status: CommandStatus::Pending,
25        }
26    }
27
28    pub fn path(&self) -> &PathBuf {
29        &self.path
30    }
31}
32
33#[async_trait]
34impl Command for SaveSessionCommand {
35    async fn execute(&mut self) -> Result<()> {
36        self.status = CommandStatus::Running;
37        debug!("Starting session save to {}", self.path.display());
38
39        let groups = self.request_group_man.read().await.list_groups().await;
40        session_serializer::save_to_file(&self.path, &groups).await?;
41
42        self.status = CommandStatus::Completed;
43        info!("Session saved to {}", self.path.display());
44        Ok(())
45    }
46
47    fn status(&self) -> CommandStatus {
48        self.status.clone()
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55    use crate::request::request_group::DownloadOptions;
56
57    #[tokio::test]
58    async fn test_save_session_command_creation() {
59        let man = Arc::new(RwLock::new(RequestGroupMan::new()));
60        let cmd = SaveSessionCommand::new(PathBuf::from("/tmp/test.sess"), man);
61        assert_eq!(cmd.status(), CommandStatus::Pending);
62        assert!(cmd.path().to_str().unwrap().contains("test.sess"));
63    }
64
65    #[tokio::test]
66    async fn test_save_session_command_execute() {
67        let man = Arc::new(RwLock::new(RequestGroupMan::new()));
68        man.write()
69            .await
70            .add_group(
71                vec!["http://example.com/file.zip".into()],
72                DownloadOptions {
73                    split: Some(4),
74                    ..Default::default()
75                },
76            )
77            .await
78            .unwrap();
79
80        let dir = std::env::temp_dir();
81        let path = dir.join(format!("test_save_session_{}.sess", std::process::id()));
82        let mut cmd = SaveSessionCommand::new(path.clone(), man);
83
84        cmd.execute().await.unwrap();
85        assert_eq!(cmd.status(), CommandStatus::Completed);
86
87        let content = tokio::fs::read_to_string(&path).await.unwrap();
88        assert!(content.contains("http://example.com/file.zip"));
89        assert!(content.contains("GID="));
90        assert!(content.contains("split=4"));
91
92        let _ = tokio::fs::remove_file(&path).await;
93    }
94
95    #[tokio::test]
96    async fn test_save_session_empty_manager() {
97        let man = Arc::new(RwLock::new(RequestGroupMan::new()));
98        let dir = std::env::temp_dir();
99        let path = dir.join(format!("test_save_empty_{}.sess", std::process::id()));
100        let mut cmd = SaveSessionCommand::new(path.clone(), man);
101
102        cmd.execute().await.unwrap();
103        assert_eq!(cmd.status(), CommandStatus::Completed);
104
105        let content = tokio::fs::read_to_string(&path).await.unwrap();
106        assert!(content.is_empty() || content.trim().is_empty());
107
108        let _ = tokio::fs::remove_file(&path).await;
109    }
110
111    #[tokio::test]
112    async fn test_save_session_atomic_write() {
113        let man = Arc::new(RwLock::new(RequestGroupMan::new()));
114        man.write()
115            .await
116            .add_group(
117                vec!["http://example.com/atomic.bin".into()],
118                DownloadOptions::default(),
119            )
120            .await
121            .unwrap();
122
123        let dir = std::env::temp_dir();
124        let path = dir.join(format!("test_atomic_{}.sess", std::process::id()));
125        let mut cmd = SaveSessionCommand::new(path.clone(), man);
126
127        cmd.execute().await.unwrap();
128
129        assert!(path.exists(), "目标文件应存在");
130        let tmp_path = path.with_extension("sess.tmp");
131        assert!(!tmp_path.exists(), "临时文件应已被 rename 删除");
132
133        let _ = tokio::fs::remove_file(&path).await;
134    }
135}