aria2_core/session/
save_session_command.rs1use 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}