systemd-transient 0.2.0

Start transient systemd services using dbus, just like systemd-run.
Documentation
use crate::error::SystemdTransientError;
use crate::watch::exit_of;
use crate::{SystemdTransientManager, TransientUnitHandle, UnitExit, UnitProperty, UnitStatus};

#[derive(Debug)]
pub struct ReusableTransientUnit {
  pub(crate) name: String,
  pub(crate) props: Vec<UnitProperty>,
  pub(crate) unit_handle: Option<TransientUnitHandle>,
  pub(crate) systemd_manager: SystemdTransientManager,
}

impl ReusableTransientUnit {
  /// The daemon decides whether the unit is running, not the cached handle,
  /// so a unit that exited on its own can be started again.
  pub async fn start(&mut self) -> Result<(), SystemdTransientError> {
    if let Some(handle) = &self.unit_handle
      && exit_of(&handle.unit.active_state().await?).is_none()
    {
      return Err(SystemdTransientError::UnitAlreadyRunning {
        name: handle.name.clone(),
      });
    }
    // The old handle pins the unit; release it before reusing the name.
    self.release().await?;

    let handle = self
      .systemd_manager
      .start(&self.name, self.props.clone())
      .await?;
    self.unit_handle = Some(handle);

    Ok(())
  }

  /// Takes over an already loaded unit of this name instead of starting
  /// one, see [`SystemdTransientManager::adopt`]. Returns whether there was
  /// one; afterwards `status`, `wait_for_exit` and `stop` work on it as after
  /// `start`. A run this value already holds is kept, and `true` is returned.
  pub async fn adopt(&mut self) -> Result<bool, SystemdTransientError> {
    if self.unit_handle.is_none() {
      self.unit_handle = self.systemd_manager.adopt(&self.name).await?;
    }
    Ok(self.unit_handle.is_some())
  }

  pub async fn stop(&mut self) -> Result<UnitExit, SystemdTransientError> {
    // Drop the handle only once the stop job succeeded.
    let exit = self.handle()?.stop().await?;
    self.release().await?;

    Ok(exit)
  }

  pub async fn wait_for_exit(&self) -> Result<UnitExit, SystemdTransientError> {
    self.handle()?.wait_for_exit().await
  }

  /// Read from the exit watch, without a D-Bus round trip.
  pub fn status(&self) -> UnitStatus {
    match &self.unit_handle {
      None => UnitStatus::Idle,
      Some(h) => h.watch.current().into(),
    }
  }

  /// Drops the reference on the last run without stopping it.
  pub async fn release(&mut self) -> Result<(), SystemdTransientError> {
    if let Some(h) = self.unit_handle.take() {
      h.release().await?;
    }
    Ok(())
  }

  fn handle(&self) -> Result<&TransientUnitHandle, SystemdTransientError> {
    self
      .unit_handle
      .as_ref()
      .ok_or_else(|| SystemdTransientError::UnitNotRunning {
        name: self.name.clone(),
      })
  }
}