watchers 1.7.1

Git integrated file watcher
use std::{env, fs, path::PathBuf};

use anyhow::{Context, Result};
use directories::ProjectDirs;
use tokio::process::Command;
use zbus::Connection;
use zbus_systemd::systemd1::ManagerProxy;

pub struct SystemdContext<'a> {
    _conn: Connection,
    manager: ManagerProxy<'a>,
}

fn get_unit_name(name: &str) -> String {
    format!("watchers@{name}.service")
}

impl<'a> SystemdContext<'a> {
    pub async fn new() -> Result<Self> {
        let conn = Connection::session().await?;
        let manager = ManagerProxy::new(&conn).await?;

        Ok(Self {
            _conn: conn,
            manager,
        })
    }

    pub async fn stop_and_disable_service(&self, name: &str) -> Result<()> {
        let unit_name = get_unit_name(name);

        self.manager
            .stop_unit(unit_name.clone(), "replace".to_string())
            .await
            .context("Failed to stop systemd service")?;

        self.manager
            .disable_unit_files(vec![unit_name.clone()], false)
            .await
            .context("Failed to enable systemd service")?;

        self.manager.reload().await?;

        Ok(())
    }

    pub async fn start_and_enable_service(&self, name: &str) -> Result<()> {
        let template_unit_path = get_systemd_unit_path();
        if !template_unit_path.is_file() {
            fs::write(template_unit_path, get_template_unit_contents())?;
        }

        let unit_name = get_unit_name(name);

        self.manager
            .start_unit(unit_name.clone(), "replace".to_string())
            .await
            .context("Failed to start systemd service")?;

        self.manager
            .enable_unit_files(vec![unit_name.clone()], false, true)
            .await
            .context("Failed to enable systemd service")?;

        self.manager.reload().await?;

        Ok(())
    }

    pub async fn get_service_logs(&self, name: &str) -> Result<String> {
        let unit_name = format!("watchers@{}.service", name);
        let output = Command::new("journalctl")
            .arg("--user")
            .arg("-u")
            .arg(unit_name)
            .output()
            .await
            .context("Failed to run journalctl")?;

        if !output.status.success() {
            return Err(anyhow::anyhow!(
                "journalctl exited with code: {}",
                output.status
            ));
        }

        Ok(String::from_utf8_lossy(&output.stdout).to_string())
    }
}

fn get_systemd_unit_path() -> PathBuf {
    let proj_dir = ProjectDirs::from("", "", "").unwrap();
    let config_dir = proj_dir.config_dir();

    PathBuf::from(format!(
        "{}/systemd/user/watchers@.service",
        config_dir.display()
    ))
}

fn get_template_unit_contents() -> String {
    let default_exe_path = "/usr/bin/local/watchers";
    let exe_path = env::current_exe().unwrap_or_else(|_| default_exe_path.into());
    format!(
        include_str!("../assets/templates/watchers@.service"),
        exe_path.as_os_str().to_str().unwrap_or(default_exe_path)
    )
}