audb_core/features/app/
stop.rs

1// Stop command implementation for Aurora OS applications
2//
3// Uses RuntimeManager D-Bus API to terminate applications via:
4// gdbus call --system --dest ru.omp.RuntimeManager
5//   --object-path /ru/omp/RuntimeManager/Control1
6//   --method ru.omp.RuntimeManager.Control1.Terminate "app_instance_id"
7//
8// Note: The app_instance_id is typically the same as the app name
9
10use crate::features::config::{device_store::DeviceStore, state::DeviceState};
11use crate::tools::{
12    macros::print_info,
13    session::DeviceSession,
14    types::DeviceIdentifier,
15};
16use anyhow::{anyhow, Context, Result};
17
18pub async fn execute(app_name: &str) -> Result<()> {
19    // Validate app_name (used as instance ID)
20    validate_app_name(app_name)?;
21
22    // Get device and establish session
23    let current_host = DeviceState::get_current()?;
24    let device_id = DeviceIdentifier::Host(current_host);
25    let device = DeviceStore::find(&device_id)?;
26
27    print_info(format!("Stopping {} on device {}", app_name, device.display_name()));
28    print_info(format!("Connecting to {}:{}...", device.host, device.port));
29
30    let mut session = DeviceSession::connect(&device)
31        .context("Failed to connect to device")?;
32
33    // Build D-Bus command to terminate app using RuntimeManager
34    // Note: Terminate takes appInstanceId, which is the same as appId
35    let stop_command = format!(
36        "gdbus call --system --dest ru.omp.RuntimeManager \
37         --object-path /ru/omp/RuntimeManager/Control1 \
38         --method ru.omp.RuntimeManager.Control1.Terminate \"{}\"",
39        app_name
40    );
41
42    print_info("Stopping application...");
43
44    let output = session.exec(&stop_command)
45        .context("Failed to stop application")?;
46
47    // Display output (usually empty on success)
48    for line in &output {
49        if !line.is_empty() {
50            println!("{}", line);
51        }
52    }
53
54    print_info("Application stopped successfully");
55    Ok(())
56}
57
58fn validate_app_name(app_name: &str) -> Result<()> {
59    if app_name.is_empty() {
60        return Err(anyhow!("App name cannot be empty"));
61    }
62    if !app_name.contains('.') {
63        return Err(anyhow!(
64            "Invalid app name: '{}'. Expected D-Bus format: ru.domain.AppName",
65            app_name
66        ));
67    }
68    if app_name.len() > 255 {
69        return Err(anyhow!("App name exceeds D-Bus limit of 255 characters"));
70    }
71    Ok(())
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    #[test]
79    fn test_validate_app_name_valid() {
80        assert!(validate_app_name("ru.auroraos.MLPackLearning").is_ok());
81        assert!(validate_app_name("com.example.App").is_ok());
82    }
83
84    #[test]
85    fn test_validate_app_name_empty() {
86        assert!(validate_app_name("").is_err());
87    }
88
89    #[test]
90    fn test_validate_app_name_no_dot() {
91        assert!(validate_app_name("InvalidAppName").is_err());
92    }
93
94    #[test]
95    fn test_validate_app_name_too_long() {
96        let long_name = "a".repeat(256);
97        assert!(validate_app_name(&long_name).is_err());
98    }
99}