aria2-core 0.2.2

High-performance download engine core: multi-protocol segmented downloads, rate limiting, config management, session persistence, and BitTorrent seeding
Documentation
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::RwLock;

use async_trait::async_trait;
use tracing::{debug, info};

use super::session_serializer;
use crate::engine::command::{Command, CommandStatus};
use crate::error::Result;
use crate::request::request_group_man::RequestGroupMan;

pub struct SaveSessionCommand {
    path: PathBuf,
    request_group_man: Arc<RwLock<RequestGroupMan>>,
    status: CommandStatus,
}

impl SaveSessionCommand {
    pub fn new(path: PathBuf, man: Arc<RwLock<RequestGroupMan>>) -> Self {
        SaveSessionCommand {
            path,
            request_group_man: man,
            status: CommandStatus::Pending,
        }
    }

    pub fn path(&self) -> &PathBuf {
        &self.path
    }
}

#[async_trait]
impl Command for SaveSessionCommand {
    async fn execute(&mut self) -> Result<()> {
        self.status = CommandStatus::Running;
        debug!("Starting session save to {}", self.path.display());

        let groups = self.request_group_man.read().await.list_groups().await;
        session_serializer::save_to_file(&self.path, &groups).await?;

        self.status = CommandStatus::Completed;
        info!("Session saved to {}", self.path.display());
        Ok(())
    }

    fn status(&self) -> CommandStatus {
        self.status.clone()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::request::request_group::DownloadOptions;

    #[tokio::test]
    async fn test_save_session_command_creation() {
        let man = Arc::new(RwLock::new(RequestGroupMan::new()));
        let cmd = SaveSessionCommand::new(PathBuf::from("/tmp/test.sess"), man);
        assert_eq!(cmd.status(), CommandStatus::Pending);
        assert!(cmd.path().to_str().unwrap().contains("test.sess"));
    }

    #[tokio::test]
    async fn test_save_session_command_execute() {
        let man = Arc::new(RwLock::new(RequestGroupMan::new()));
        man.write()
            .await
            .add_group(
                vec!["http://example.com/file.zip".into()],
                DownloadOptions {
                    split: Some(4),
                    ..Default::default()
                },
            )
            .await
            .unwrap();

        let dir = std::env::temp_dir();
        let path = dir.join(format!("test_save_session_{}.sess", std::process::id()));
        let mut cmd = SaveSessionCommand::new(path.clone(), man);

        cmd.execute().await.unwrap();
        assert_eq!(cmd.status(), CommandStatus::Completed);

        let content = tokio::fs::read_to_string(&path).await.unwrap();
        assert!(content.contains("http://example.com/file.zip"));
        assert!(content.contains("GID="));
        assert!(content.contains("split=4"));

        let _ = tokio::fs::remove_file(&path).await;
    }

    #[tokio::test]
    async fn test_save_session_empty_manager() {
        let man = Arc::new(RwLock::new(RequestGroupMan::new()));
        let dir = std::env::temp_dir();
        let path = dir.join(format!("test_save_empty_{}.sess", std::process::id()));
        let mut cmd = SaveSessionCommand::new(path.clone(), man);

        cmd.execute().await.unwrap();
        assert_eq!(cmd.status(), CommandStatus::Completed);

        let content = tokio::fs::read_to_string(&path).await.unwrap();
        assert!(content.is_empty() || content.trim().is_empty());

        let _ = tokio::fs::remove_file(&path).await;
    }

    #[tokio::test]
    async fn test_save_session_atomic_write() {
        let man = Arc::new(RwLock::new(RequestGroupMan::new()));
        man.write()
            .await
            .add_group(
                vec!["http://example.com/atomic.bin".into()],
                DownloadOptions::default(),
            )
            .await
            .unwrap();

        let dir = std::env::temp_dir();
        let path = dir.join(format!("test_atomic_{}.sess", std::process::id()));
        let mut cmd = SaveSessionCommand::new(path.clone(), man);

        cmd.execute().await.unwrap();

        assert!(path.exists(), "目标文件应存在");
        let tmp_path = path.with_extension("sess.tmp");
        assert!(!tmp_path.exists(), "临时文件应已被 rename 删除");

        let _ = tokio::fs::remove_file(&path).await;
    }
}