mod common;
pub mod log;
pub mod parser;
pub mod tests;
use common::trim_str;
use log::Logger;
use parser::CliArgs;
use std::{collections::HashMap, error::Error, fs, process::Command};
use zbus::{connection, interface, Connection, SignalContext};
#[derive(Debug, PartialEq, Eq)]
pub struct ResourceManager {
resources: HashMap<String, String>,
preprocessor: String,
logger: Logger,
args: CliArgs,
}
impl ResourceManager {
pub fn from_args(args: &CliArgs) -> ResourceManager {
let command = match &args.cpp {
Some(cmd) => cmd.clone(),
_ => String::from("/usr/bin/cpp"),
};
let resources = HashMap::new();
let preprocessor = command;
let logger = Logger::from(args);
ResourceManager {
resources,
preprocessor,
logger,
args: args.clone(),
}
}
pub fn init(&mut self) {
self.logger.info("Initializing Daemon...");
let filename = match &self.args.load {
Some(file) => file,
None => match &self.args.filename {
Some(x) => x,
None => return,
},
};
self.load_from_file(
&filename.clone(),
self.args.nocpp,
&self.preprocessor.clone(),
"",
);
}
pub async fn run_server(self) -> zbus::Result<Connection> {
connection::Builder::session()?
.name("org.regolith.Trawl")?
.serve_at("/org/regolith/Trawl", self)?
.build()
.await
}
fn preprocessor(&self, cmd: &str) -> Command {
Command::new(cmd)
}
fn get_preprocessed_file(
&mut self,
file_path: &str,
nocpp: bool,
cpp: &str,
cpp_args: &str,
) -> Result<String, Box<dyn Error>> {
self.logger.info(&format!("{cpp} {cpp_args} {file_path}"));
if nocpp {
self.logger
.warn("wont use preprocessor - try running without --nocpp flag");
let config_str = fs::read_to_string(file_path)?;
self.logger.info("Config file read successfully");
self.logger.info(&config_str);
return Ok(config_str);
}
let cmd_args = if cpp_args.trim() == "" {
[file_path].to_vec()
} else {
let mut args: Vec<&str> = cpp_args.split(" ").collect();
args.append(&mut [file_path].to_vec());
args
};
let output_bytes = self.preprocessor(cpp).args(cmd_args).output()?.stdout;
let conf_utf8 = String::from_utf8(output_bytes)?;
self.logger.info("File preprocessed successfully...");
self.logger.info(&conf_utf8);
Ok(conf_utf8)
}
fn check_valid_key(&self, key: &str) -> bool {
let mut is_valid = true;
let allowed_chars = ['-', '.', '_'];
if key.len() == 0 {
is_valid = false;
}
for ch in key.chars() {
if !ch.is_ascii_alphanumeric() && !allowed_chars.contains(&ch) {
self.logger.warn(&format!("{key} is not a valid key"));
is_valid = false;
break;
}
}
is_valid
}
fn parse_config(&self, config_str: &str) -> HashMap<String, String> {
let parsed_resources: HashMap<String, String> = config_str
.lines()
.filter_map(|s| s.split_once(':'))
.map(|(k, v)| (k.trim().to_string(), v.trim().to_string()))
.filter(|(k, _)| self.check_valid_key(&k))
.collect();
parsed_resources
}
fn load_from_file(&mut self, file: &str, nocpp: bool, cpp: &str, args: &str) {
let config_str = match self.get_preprocessed_file(file, nocpp, cpp, args) {
Ok(conf) => conf,
Err(e) => {
self.logger.from_error(e);
return;
}
};
let parsed_resources = self.parse_config(&config_str);
self.logger
.info(&format!("parsed_resources: {:#?}", parsed_resources));
for (k, v) in parsed_resources {
self.resources.entry(k).or_insert(v);
}
self.logger.info(&format!(
"Updated resources after loading: \n {:#?}",
&self.resources
))
}
fn merge_from_file(&mut self, file: &str, nocpp: bool, cpp: &str, args: &str) {
let config_str = match self.get_preprocessed_file(file, nocpp, cpp, args) {
Ok(conf) => conf,
Err(e) => {
self.logger.from_error(e);
return;
}
};
let parsed_resources = self.parse_config(&config_str);
self.logger
.info(&format!("parsed_resources: {:#?}", parsed_resources));
for (k, v) in parsed_resources {
self.resources.insert(k, v);
}
self.logger.info(&format!(
"Updated resources after merging: \n {:#?}",
&self.resources
))
}
pub async fn emit_resources_changed(&self, ctxt: &SignalContext<'_>) {
if let Err(e) = self.resources_changed(&ctxt).await {
self.logger.error(&format!("{e}"));
}
}
pub fn handle_remove_all(&mut self) {
self.resources.clear();
}
pub fn handle_remove_one(&mut self, key: &str) -> Option<(String, String)> {
self.resources.remove_entry(key)
}
}
#[interface(name = "org.regolith.trawl1")]
impl ResourceManager {
async fn load(
&mut self,
#[zbus(signal_context)] ctxt: SignalContext<'_>,
path: &str,
nocpp: bool,
) {
self.load_from_file(path, nocpp, &self.preprocessor.clone(), "");
self.emit_resources_changed(&ctxt).await;
}
async fn merge(
&mut self,
#[zbus(signal_context)] ctxt: SignalContext<'_>,
path: &str,
nocpp: bool,
) {
self.merge_from_file(path, nocpp, &self.preprocessor.clone(), "");
self.emit_resources_changed(&ctxt).await;
}
async fn load_cpp(
&mut self,
#[zbus(signal_context)] ctxt: SignalContext<'_>,
path: &str,
cpp: &str,
args: &str,
) {
self.load_from_file(path, false, cpp, args);
self.emit_resources_changed(&ctxt).await;
}
async fn merge_cpp(
&mut self,
#[zbus(signal_context)] ctxt: SignalContext<'_>,
path: &str,
cpp: &str,
args: &str,
) {
self.merge_from_file(path, false, cpp, args);
self.emit_resources_changed(&ctxt).await;
}
pub fn query(&self, q: &str) -> String {
let query_trimmed = trim_str(q);
let mut matches: Vec<_> = self
.resources
.iter()
.filter(|(k, _)| k.contains(query_trimmed))
.map(|(x, v)| format!("{} :\t{}", x, v))
.collect();
matches.sort();
let query_result = matches.join("\n");
self.logger.info(&format!(
"Following resources match the query '{query_trimmed}'\
: {query_result}"
));
query_result
}
pub fn get_resource(&self, key: &str) -> String {
let key_trimmed = trim_str(key);
let value = self
.resources
.get(key_trimmed)
.unwrap_or(&String::from(""))
.to_owned();
self.logger
.info(&format!("value of key {key_trimmed} is {value}"));
value
}
pub async fn set_resource(
&mut self,
#[zbus(signal_context)] ctxt: SignalContext<'_>,
key: String,
val: String,
) {
let key_trimmed = common::trim_str(&key);
let val_trimmed = common::trim_str(&val);
let curr_val = self.resources.get(key_trimmed);
if let Some(x) = curr_val {
if *x == val {
return;
}
}
self.resources
.insert(String::from(key_trimmed), String::from(val_trimmed));
self.emit_resources_changed(&ctxt).await;
}
pub async fn add_resource(
&mut self,
#[zbus(signal_context)] ctxt: SignalContext<'_>,
key: String,
val: String,
) {
let key_trimmed = trim_str(&key);
let val_trimmed = trim_str(&val);
let curr_val = self.resources.get(key_trimmed);
if let Some(_) = curr_val {
return;
}
self.resources
.insert(String::from(key_trimmed), String::from(val_trimmed));
self.emit_resources_changed(&ctxt).await;
}
pub async fn remove_all(&mut self, #[zbus(signal_context)] ctxt: SignalContext<'_>) {
let num_of_resources = self.resources.len();
self.resources.clear();
if num_of_resources > 0 {
self.emit_resources_changed(&ctxt).await;
}
self.logger.warn("All resources cleared");
}
pub async fn remove_one(&mut self, #[zbus(signal_context)] ctxt: SignalContext<'_>, key: &str) {
let key_trimmed = trim_str(key);
let removed_entry = self.handle_remove_one(key_trimmed);
if let Some(pair) = removed_entry {
self.emit_resources_changed(&ctxt).await;
self.logger.warn(&format!("Resource cleared {:?}", pair));
}
}
#[zbus(property)]
pub fn resources(&self) -> HashMap<String, String> {
self.resources.clone()
}
}