use async_std::path::Path; use async_std::prelude::*; use std::cell::Cell; use crate::utils::asyn::IntoResults; use std::io::{ErrorKind, Error}; use async_std::fs::{read_to_string, read_dir, ReadDir, DirEntry}; use async_std::task; use crate::config::Config; use std::collections::HashSet; use crate::unit::Unit; #[derive(Debug, Clone, Eq, PartialEq)] pub struct LoaderConfig { pub locations: Vec, } impl LoaderConfig { pub fn default() -> Self { LoaderConfig { locations: vec![ "etc/systemd/system/".to_string(), "run/systemd/system/".to_string(), "usr/lib/systemd/system/".to_string(), ] } } } #[derive(Debug, Clone)] pub struct Loader { root: String, config: LoaderConfig, } impl Default for Loader { fn default() -> Self { Self::new("/", LoaderConfig::default()) } } impl Loader { pub fn with_root(root: &str) -> Loader { Self::new(root, LoaderConfig::default()) } pub fn new(root: &str, config: LoaderConfig) -> Loader { Loader { root: root.to_string(), config } } pub async fn load_unit(&self, unit_name: &str) -> Option { let config = self.load_config(unit_name).await; let mut unit = config.get_unit()?; let read_items = |postfix: String| { async { let futures = self.config .locations .iter() .map(move |loc| { let path = Path::new(&self.root).join(loc).join(format!("{}.{}", unit_name, postfix)); read_dir(path) }) .collect::>() .into_results() .await .into_iter() .filter_map(|result: Result| { result.ok() }) .collect::>(); let mut results = vec![]; for future in futures { let mut reader = future; while let Some(item) = reader.next().await { if let Ok(item) = item { if let Some(name) = item.file_name().to_str() { results.push(String::from(name)); } } } } results } }; unit.requires.append(&mut read_items("requires".to_string()).await); unit.wants.append(&mut read_items("wants".to_string()).await); Some(unit) } pub async fn load_config(&self, unit_name: &str) -> Config { let mut futures = Vec::new(); let mut paths = HashSet::new(); let mut config_dirs = HashSet::new(); for location in &self.config.locations { let current_loc = Path::new(&self.root).join(&location); paths.insert(current_loc.join(unit_name)); let (unit_type, name) = { let mut items = unit_name.rsplitn(2, "."); (items.next().unwrap(), items.next().unwrap()) }; let parts: Vec<&str> = name.split("-").collect(); let mut builder = String::new(); for part_index in 0..parts.len() - 1 { let part = parts[part_index]; builder += part; builder += "-"; config_dirs.insert(current_loc.join(format!("{}.{}.d", builder, unit_type))); } // for unit test@nice.service it should also check test@.service.d if name.contains('@') && !name.ends_with('@') { let mut parts = name.splitn(2, '@'); config_dirs.insert(current_loc.join(format!("{}@.{}.d", parts.next().unwrap(), unit_type))); } config_dirs.insert(current_loc.join(unit_name.to_string() + ".d")); } for config_dir in config_dirs { let dir_res: Result = read_dir(config_dir).await; match dir_res { Ok(mut dir) => { while let Some(item) = dir.next().await { if item.is_err() { continue; } let item: DirEntry = item.unwrap(); if item.path().extension().map(|x| x == "conf").unwrap_or(false) { paths.insert(item.path()); } } } Err(err) => { if err.kind() != ErrorKind::NotFound { // Log? } } } } for location in paths { let path = location.to_str().unwrap().to_string(); futures.push(async { let result: Result = read_to_string(&path).await; match result { Ok(contents) => { Some((path, contents)) } Err(err) => { if err.kind() != ErrorKind::NotFound { // Log? } None } } }); } let mut results: Vec<(String, String)> = futures .into_results().await .into_iter() .filter(|o| o.is_some()) .map(|x| x.unwrap()) .collect(); let mut config = Config::new(unit_name); let mut configs = Vec::new(); results.sort(); for (path, contents) in results { if path.ends_with(".conf") { configs.push((path, contents)); continue; } config.load_file(&path, &contents).unwrap(); } for (path, contents) in configs { config.load_file(&path, &contents).unwrap(); } return config; } }