#![allow(clippy::many_single_char_names)]
#![allow(dead_code)]
#![allow(unused_macros)]
extern crate console;
extern crate indicatif;
extern crate log;
extern crate shellexpand;
extern crate simplelog;
extern crate solvent;
extern crate symlink;
extern crate sys_info;
pub mod config;
mod hm_macro;
pub mod hmerror;
use config::{ManagedObject, Worker};
use hmerror::{ErrorKind as hmek, HMError};
use console::{pad_str, style, Alignment};
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use log::{info, warn};
use solvent::DepGraph;
use std::{
collections::{HashMap, HashSet},
fmt,
fs::{copy, create_dir_all, metadata, remove_dir_all, remove_file},
io::{BufRead, BufReader, Error, ErrorKind},
path::Path,
process::{exit, Command, Stdio},
sync::mpsc::{self, Sender},
{thread, time},
};
use symlink::{symlink_dir as sd, symlink_file as sf};
#[derive(Debug, Clone)]
struct SneakyDepGraphImposter<String> {
nodes: Vec<String>,
dependencies: HashMap<usize, HashSet<usize>>,
satisfied: HashSet<usize>,
}
impl fmt::Display for SneakyDepGraphImposter<String> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut i: usize = 0;
for n in self.nodes.clone() {
if self.dependencies.get(&i).is_some() {
let _ = write!(f, "[ {} -> ", n);
#[allow(clippy::for_loops_over_fallibles)]
for d in self.dependencies.get(&i) {
if d.is_empty() {
write!(f, "<no deps> ")?;
}
let mut j: usize = 1;
for m in d {
if j == d.len() {
write!(f, "{} ", self.nodes[*m])?;
} else {
write!(f, "{}, ", self.nodes[*m])?;
}
j += 1;
}
}
}
i += 1;
if i != self.nodes.len() {
write!(f, "], ")?;
} else {
write!(f, "]")?;
}
}
Ok(())
}
}
pub fn copy_item(source: String, target: String, force: bool) -> Result<(), HMError> {
let _lsource: String = shellexpand::tilde(&source).to_string();
let _ltarget: String = shellexpand::tilde(&target).to_string();
let md = match metadata(_lsource.clone()) {
Ok(a) => a,
Err(e) => return Err(HMError::Io(e)),
};
if force && Path::new(_ltarget.as_str()).exists() {
if md.is_dir() {
remove_dir_all(_ltarget.clone())?;
} else {
remove_file(_ltarget.clone())?;
}
}
copy(Path::new(_lsource.as_str()), Path::new(_ltarget.as_str()))?;
Ok(())
}
pub fn symlink_item(source: String, target: String, force: bool) -> Result<(), HMError> {
let _lsource: String = shellexpand::tilde(&source).to_string();
let _ltarget: String = shellexpand::tilde(&target).to_string();
let md = match metadata(_lsource.clone()) {
Ok(a) => a,
Err(e) => return Err(HMError::Io(e)),
};
let lmd = metadata(_ltarget.clone());
if force && Path::new(_ltarget.as_str()).exists() {
if lmd.unwrap().is_dir() {
remove_dir_all(_ltarget.clone())?;
} else {
remove_file(_ltarget.clone())?;
}
}
create_dir_all(Path::new(_ltarget.as_str()).parent().unwrap())?;
if md.is_dir() {
sd(Path::new(_lsource.as_str()), Path::new(_ltarget.as_str()))?;
} else if md.is_file() {
sf(Path::new(_lsource.as_str()), Path::new(_ltarget.as_str()))?;
}
Ok(())
}
pub fn send_tasks_off_to_college(
mo: &ManagedObject,
tx: &Sender<Worker>,
p: ProgressBar,
) -> Result<(), Error> {
let s: String = mo.solution.clone();
let s1: String = mo.solution.clone();
let n: String = mo.name.clone();
let tx1: Sender<Worker> = Sender::clone(tx);
let _: thread::JoinHandle<Result<(), HMError>> = thread::spawn(move || {
let mut c = Command::new("bash")
.arg("-c")
.arg(s)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
let output: std::process::ChildStdout = c.stdout.take().unwrap();
let reader: BufReader<std::process::ChildStdout> = BufReader::new(output);
thread::spawn(|| {
reader
.lines()
.filter_map(|line| line.ok())
.for_each(|line| info!("{}", line));
});
p.set_style(
ProgressStyle::default_spinner()
.template("[{elapsed:4}] {prefix:.bold.dim} {spinner} {wide_msg}"),
);
p.enable_steady_tick(200);
let x = pad_str(format!("task {}", n).as_str(), 30, Alignment::Left, None).into_owned();
p.set_prefix(x);
loop {
let mut w: Worker = Worker {
name: n.clone(),
status: None,
completed: false,
};
match c.try_wait() {
Ok(Some(status)) => {
if status.success() {
p.finish_with_message(console::style("✓").green().to_string());
w.status = status.code();
w.completed = true;
tx1.send(w).unwrap();
info!("Successfully completed {}.", n);
return Ok(());
} else {
drop(tx1);
warn!("Error within `{}`", s1);
p.abandon_with_message(console::style("✗").red().to_string());
return Err(HMError::Regular(hmek::SolutionError { solution: s1 }));
}
} Ok(None) => {
tx1.send(w).unwrap();
thread::sleep(time::Duration::from_millis(200));
}
Err(_e) => {
drop(tx1);
p.abandon_with_message(console::style("✗").red().to_string());
return Err(HMError::Regular(hmek::SolutionError { solution: s1 }));
}
}
}
});
Ok(())
}
pub fn get_task_batches(
mut nodes: HashMap<String, ManagedObject>,
target_task: Option<String>,
) -> Result<Vec<Vec<ManagedObject>>, HMError> {
let our_os = config::determine_os();
let mut depgraph: DepGraph<String> = DepGraph::new();
let mut nodes_to_remove: Vec<String> = Vec::new();
let mut wrong_platforms: HashMap<String, config::OS> = HashMap::new();
for (name, node) in &nodes {
if node.os.is_none() || node.os.clone().unwrap() == our_os {
depgraph.register_dependencies(name.to_owned(), node.dependencies.clone());
} else {
nodes_to_remove.push(name.to_string());
wrong_platforms.insert(name.clone(), node.os.clone().unwrap());
}
}
for n in nodes_to_remove {
nodes.remove(&n);
}
let mut tasks: Vec<Vec<ManagedObject>> = Vec::new();
let mut _dedup: HashSet<String> = HashSet::new();
if let Some(tt_name) = target_task {
let mut qtdg: Vec<ManagedObject> = Vec::new();
let tdg: solvent::DepGraphIterator<String> = match depgraph.dependencies_of(&tt_name) {
Ok(i) => i,
Err(_) => {
return Err(HMError::Regular(hmek::DependencyUndefinedError {
dependency: tt_name,
}));
}
};
for n in tdg {
match n {
Ok(r) => {
let mut a = match nodes.get(r) {
Some(a) => a,
None => {
if wrong_platforms.contains_key(r) {
return Err(HMError::Regular(hmek::IncorrectPlatformError {
dependency: String::from(r),
platform: our_os,
target_platform: wrong_platforms.get(r).cloned().unwrap(),
}));
} else {
return Err(HMError::Regular(hmek::DependencyUndefinedError {
dependency: String::from(r),
}));
}
}
}
.to_owned();
a.set_satisfied();
qtdg.push(a);
}
Err(_e) => unsafe {
let my_sneaky_depgraph: SneakyDepGraphImposter<String> = std::mem::transmute(depgraph);
return Err(HMError::Regular(hmek::CyclicalDependencyError {
dependency_graph: my_sneaky_depgraph.to_string(),
}));
},
}
}
tasks.push(qtdg);
} else {
for name in nodes.keys() {
let mut q: Vec<ManagedObject> = Vec::new();
let dg: solvent::DepGraphIterator<String> = depgraph.dependencies_of(name).unwrap();
for n in dg {
match n {
Ok(r) => {
let c = String::from(r.as_str());
if _dedup.insert(c) {
let mut a = match nodes.get(r) {
Some(a) => a,
None => {
if wrong_platforms.contains_key(r) {
return Err(HMError::Regular(hmek::IncorrectPlatformError {
dependency: String::from(r),
platform: our_os,
target_platform: wrong_platforms.get(r).cloned().unwrap(),
}));
} else {
return Err(HMError::Regular(hmek::DependencyUndefinedError {
dependency: String::from(r),
}));
}
}
}
.to_owned();
a.set_satisfied();
q.push(a);
}
}
Err(_e) => unsafe {
let my_sneaky_depgraph: SneakyDepGraphImposter<String> = std::mem::transmute(depgraph);
return Err(HMError::Regular(hmek::CyclicalDependencyError {
dependency_graph: my_sneaky_depgraph.to_string(),
}));
},
}
}
tasks.push(q);
}
}
Ok(tasks)
}
fn execute_solution(solution: String) -> Result<(), HMError> {
let child: thread::JoinHandle<Result<(), HMError>> = thread::spawn(|| {
let output = Command::new("bash")
.arg("-c")
.arg(solution)
.stdout(Stdio::piped())
.spawn()?
.stdout
.ok_or_else(|| Error::new(ErrorKind::Other, "Couldn't capture stdout"))?;
if cfg!(debug_assertions) {
let reader = BufReader::new(output);
reader
.lines()
.filter_map(|line| line.ok())
.for_each(|line| println!("{}", line));
}
Ok(())
});
child.join().unwrap()
}
pub fn perform_operation_on(mo: ManagedObject) -> Result<(), HMError> {
let _s = mo.method.as_str();
match _s {
"symlink" => {
let source: String = mo.source;
let destination: String = mo.destination;
symlink_item(source, destination, mo.force)
}
"copy" => {
let source: String = mo.source;
let destination: String = mo.destination;
copy_item(source, destination, mo.force)
}
_ => {
println!("{}", style(_s.to_string()).red());
Ok(())
}
}
}
pub fn do_tasks(
a: HashMap<String, config::ManagedObject>,
target_task: Option<String>,
) -> Result<(), HMError> {
let mut complex_operations = a.clone();
let mut simple_operations = a;
complex_operations.retain(|_, v| v.is_task()); simple_operations.retain(|_, v| !v.is_task()); if target_task.is_some() {
let tt_name = target_task.clone().unwrap();
simple_operations.retain(|_, v| v.name == tt_name);
}
for (_name, _mo) in simple_operations.into_iter() {
let p = _mo.post.clone();
let a = perform_operation_on(_mo).map_err(|e| {
hmerror::error(
format!("Failed to perform operation on {:#?}", _name).as_str(),
e.to_string().as_str(),
)
});
if a.is_ok() {
hmerror::happy_print(format!("Successfully performed operation on {:#?}", _name).as_str());
if !p.is_empty() {
println!("↳ Executing post {} for {}... ", p, _name);
let _ = execute_solution(p);
}
}
}
let (tx, rx) = mpsc::channel();
let mp: MultiProgress = MultiProgress::new();
let mut t: HashSet<String> = HashSet::new();
let _v = get_task_batches(complex_operations, target_task).unwrap_or_else(|er| {
hmerror::error(
"Error occurred attempting to get task batches",
format!("{}{}", "\n", er.to_string().as_str()).as_str(),
);
exit(3);
});
for _a in _v {
for _b in _a {
t.insert(_b.name.to_string());
let _p: ProgressBar = mp.add(ProgressBar::new_spinner());
send_tasks_off_to_college(&_b, &tx, _p).expect("ohtehnoes");
}
}
let mut v: HashMap<String, config::Worker> = HashMap::new();
loop {
if let Ok(_t) = rx.try_recv() {
v.insert(_t.name.clone(), _t.clone());
}
std::thread::sleep(time::Duration::from_millis(10));
if !all_workers_done(&v) {
continue;
}
break;
}
mp.join().unwrap();
Ok(())
}
fn all_workers_done(workers: &HashMap<String, config::Worker>) -> bool {
for w in workers.values() {
if !w.completed {
return false;
}
}
true
}