1#![allow(clippy::many_single_char_names)]
64#![allow(dead_code)]
65#![allow(unused_macros)]
66extern crate console;
67extern crate indicatif;
68extern crate log;
69extern crate shellexpand;
70extern crate simplelog;
71extern crate solvent;
72extern crate symlink;
73extern crate sys_info;
74
75pub mod config;
76mod hm_macro;
77pub mod hmerror;
78
79use config::{ManagedObject, Worker};
80use hmerror::{ErrorKind as hmek, HMError};
81
82use console::{pad_str, style, Alignment};
83use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
84use log::{info, warn};
85use solvent::DepGraph;
86use std::{
87 collections::{HashMap, HashSet},
88 fmt,
89 fs::{copy, create_dir_all, metadata, remove_dir_all, remove_file},
90 io::{BufRead, BufReader, Error, ErrorKind},
91 path::Path,
92 process::{exit, Command, Stdio},
93 sync::mpsc::{self, Sender},
94 {thread, time},
95};
96use symlink::{symlink_dir as sd, symlink_file as sf};
97
98#[derive(Debug, Clone)]
102struct SneakyDepGraphImposter<String> {
103 nodes: Vec<String>,
104 dependencies: HashMap<usize, HashSet<usize>>,
105 satisfied: HashSet<usize>,
106}
107
108impl fmt::Display for SneakyDepGraphImposter<String> {
109 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
110 let mut i: usize = 0;
111 for n in self.nodes.clone() {
112 if self.dependencies.get(&i).is_some() {
113 let _ = write!(f, "[ {} -> ", n);
114 #[allow(clippy::for_loops_over_fallibles)]
115 for d in self.dependencies.get(&i) {
116 if d.is_empty() {
117 write!(f, "<no deps> ")?;
118 }
119 let mut j: usize = 1;
120 for m in d {
121 if j == d.len() {
122 write!(f, "{} ", self.nodes[*m])?;
123 } else {
124 write!(f, "{}, ", self.nodes[*m])?;
125 }
126 j += 1;
127 }
128 }
129 }
130 i += 1;
131 if i != self.nodes.len() {
132 write!(f, "], ")?;
133 } else {
134 write!(f, "]")?;
135 }
136 }
137 Ok(())
138 }
139}
140
141pub fn copy_item(source: String, target: String, force: bool) -> Result<(), HMError> {
147 let _lsource: String = shellexpand::tilde(&source).to_string();
148 let _ltarget: String = shellexpand::tilde(&target).to_string();
149 let md = match metadata(_lsource.clone()) {
150 Ok(a) => a,
151 Err(e) => return Err(HMError::Io(e)),
152 };
153 if force && Path::new(_ltarget.as_str()).exists() {
154 if md.is_dir() {
155 remove_dir_all(_ltarget.clone())?;
156 } else {
157 remove_file(_ltarget.clone())?;
158 }
159 }
160 copy(Path::new(_lsource.as_str()), Path::new(_ltarget.as_str()))?;
161 Ok(())
162}
163
164pub fn symlink_item(source: String, target: String, force: bool) -> Result<(), HMError> {
170 let _lsource: String = shellexpand::tilde(&source).to_string();
171 let _ltarget: String = shellexpand::tilde(&target).to_string();
172 let md = match metadata(_lsource.clone()) {
173 Ok(a) => a,
174 Err(e) => return Err(HMError::Io(e)),
175 };
176 let lmd = metadata(_ltarget.clone());
177 if force && Path::new(_ltarget.as_str()).exists() {
178 if lmd.unwrap().is_dir() {
180 remove_dir_all(_ltarget.clone())?;
181 } else {
182 remove_file(_ltarget.clone())?;
183 }
184 }
185 create_dir_all(Path::new(_ltarget.as_str()).parent().unwrap())?;
187 if md.is_dir() {
188 sd(Path::new(_lsource.as_str()), Path::new(_ltarget.as_str()))?;
189 } else if md.is_file() {
190 sf(Path::new(_lsource.as_str()), Path::new(_ltarget.as_str()))?;
191 }
192 Ok(())
193}
194
195pub fn send_tasks_off_to_college(
205 mo: &ManagedObject,
206 tx: &Sender<Worker>,
207 p: ProgressBar,
208) -> Result<(), Error> {
209 let s: String = mo.solution.clone();
210 let s1: String = mo.solution.clone();
211 let n: String = mo.name.clone();
212 let tx1: Sender<Worker> = Sender::clone(tx);
213 let _: thread::JoinHandle<Result<(), HMError>> = thread::spawn(move || {
214 let mut c = Command::new("bash")
215 .arg("-c")
216 .arg(s)
217 .stdout(Stdio::piped())
218 .stderr(Stdio::piped())
219 .spawn()
220 .unwrap();
221 let output: std::process::ChildStdout = c.stdout.take().unwrap();
222 let reader: BufReader<std::process::ChildStdout> = BufReader::new(output);
223 thread::spawn(|| {
226 reader
227 .lines()
228 .filter_map(|line| line.ok())
229 .for_each(|line| info!("{}", line));
230 });
231 p.set_style(
232 ProgressStyle::default_spinner()
233 .template("[{elapsed:4}] {prefix:.bold.dim} {spinner} {wide_msg}"),
234 );
235 p.enable_steady_tick(200);
236 let x = pad_str(format!("task {}", n).as_str(), 30, Alignment::Left, None).into_owned();
237 p.set_prefix(x);
238 loop {
239 let mut w: Worker = Worker {
240 name: n.clone(),
241 status: None,
242 completed: false,
243 };
244 match c.try_wait() {
245 Ok(Some(status)) => {
247 if status.success() {
248 p.finish_with_message(console::style("✓").green().to_string());
250 w.status = status.code();
251 w.completed = true;
252 tx1.send(w).unwrap();
253 info!("Successfully completed {}.", n);
254 return Ok(());
255 } else {
256 drop(tx1);
258 warn!("Error within `{}`", s1);
259 p.abandon_with_message(console::style("✗").red().to_string());
260 return Err(HMError::Regular(hmek::SolutionError { solution: s1 }));
261 }
262 } Ok(None) => {
264 tx1.send(w).unwrap();
265 thread::sleep(time::Duration::from_millis(200));
266 }
267 Err(_e) => {
268 drop(tx1);
270 p.abandon_with_message(console::style("✗").red().to_string());
271 return Err(HMError::Regular(hmek::SolutionError { solution: s1 }));
272 }
273 }
274 }
275 });
276 Ok(())
277}
278
279pub fn get_task_batches(
309 mut nodes: HashMap<String, ManagedObject>,
310 target_task: Option<String>,
311) -> Result<Vec<Vec<ManagedObject>>, HMError> {
312 let our_os = config::determine_os();
313 let mut depgraph: DepGraph<String> = DepGraph::new();
314 let mut nodes_to_remove: Vec<String> = Vec::new();
315 let mut wrong_platforms: HashMap<String, config::OS> = HashMap::new();
316 for (name, node) in &nodes {
317 if node.os.is_none() || node.os.clone().unwrap() == our_os {
318 depgraph.register_dependencies(name.to_owned(), node.dependencies.clone());
319 } else {
320 nodes_to_remove.push(name.to_string());
321 wrong_platforms.insert(name.clone(), node.os.clone().unwrap());
322 }
323 }
324 for n in nodes_to_remove {
325 nodes.remove(&n);
326 }
327 let mut tasks: Vec<Vec<ManagedObject>> = Vec::new();
328 let mut _dedup: HashSet<String> = HashSet::new();
329 if let Some(tt_name) = target_task {
335 let mut qtdg: Vec<ManagedObject> = Vec::new();
338 let tdg: solvent::DepGraphIterator<String> = match depgraph.dependencies_of(&tt_name) {
339 Ok(i) => i,
340 Err(_) => {
341 return Err(HMError::Regular(hmek::DependencyUndefinedError {
342 dependency: tt_name,
343 }));
344 }
345 };
346 for n in tdg {
347 match n {
348 Ok(r) => {
349 let mut a = match nodes.get(r) {
350 Some(a) => a,
351 None => {
352 if wrong_platforms.contains_key(r) {
358 return Err(HMError::Regular(hmek::IncorrectPlatformError {
359 dependency: String::from(r),
360 platform: our_os,
361 target_platform: wrong_platforms.get(r).cloned().unwrap(),
362 }));
363 } else {
364 return Err(HMError::Regular(hmek::DependencyUndefinedError {
365 dependency: String::from(r),
366 }));
367 }
368 }
369 }
370 .to_owned();
371 a.set_satisfied();
372 qtdg.push(a);
373 }
374 Err(_e) => unsafe {
375 let my_sneaky_depgraph: SneakyDepGraphImposter<String> = std::mem::transmute(depgraph);
379 return Err(HMError::Regular(hmek::CyclicalDependencyError {
380 dependency_graph: my_sneaky_depgraph.to_string(),
382 }));
383 },
384 }
385 }
386 tasks.push(qtdg);
387 } else {
388 for name in nodes.keys() {
391 let mut q: Vec<ManagedObject> = Vec::new();
392 let dg: solvent::DepGraphIterator<String> = depgraph.dependencies_of(name).unwrap();
393 for n in dg {
394 match n {
395 Ok(r) => {
396 let c = String::from(r.as_str());
397 if _dedup.insert(c) {
399 let mut a = match nodes.get(r) {
400 Some(a) => a,
401 None => {
402 if wrong_platforms.contains_key(r) {
408 return Err(HMError::Regular(hmek::IncorrectPlatformError {
409 dependency: String::from(r),
410 platform: our_os,
411 target_platform: wrong_platforms.get(r).cloned().unwrap(),
412 }));
413 } else {
414 return Err(HMError::Regular(hmek::DependencyUndefinedError {
415 dependency: String::from(r),
416 }));
417 }
418 }
419 }
420 .to_owned();
421 a.set_satisfied();
422 q.push(a);
423 }
424 }
425 Err(_e) => unsafe {
426 let my_sneaky_depgraph: SneakyDepGraphImposter<String> = std::mem::transmute(depgraph);
430 return Err(HMError::Regular(hmek::CyclicalDependencyError {
431 dependency_graph: my_sneaky_depgraph.to_string(),
433 }));
434 },
435 }
436 }
437 tasks.push(q);
438 }
439 }
440 Ok(tasks)
441}
442
443fn execute_solution(solution: String) -> Result<(), HMError> {
447 let child: thread::JoinHandle<Result<(), HMError>> = thread::spawn(|| {
451 let output = Command::new("bash")
452 .arg("-c")
453 .arg(solution)
454 .stdout(Stdio::piped())
455 .spawn()?
456 .stdout
457 .ok_or_else(|| Error::new(ErrorKind::Other, "Couldn't capture stdout"))?;
458 if cfg!(debug_assertions) {
459 let reader = BufReader::new(output);
460 reader
461 .lines()
462 .filter_map(|line| line.ok())
463 .for_each(|line| println!("{}", line));
464 }
465 Ok(())
466 });
467 child.join().unwrap()
468}
469
470pub fn perform_operation_on(mo: ManagedObject) -> Result<(), HMError> {
475 let _s = mo.method.as_str();
476 match _s {
477 "symlink" => {
478 let source: String = mo.source;
479 let destination: String = mo.destination;
480 symlink_item(source, destination, mo.force)
481 }
482 "copy" => {
483 let source: String = mo.source;
484 let destination: String = mo.destination;
485 copy_item(source, destination, mo.force)
486 }
487 _ => {
488 println!("{}", style(_s.to_string()).red());
489 Ok(())
490 }
491 }
492}
493
494pub fn do_tasks(
504 a: HashMap<String, config::ManagedObject>,
505 target_task: Option<String>,
506) -> Result<(), HMError> {
507 let mut complex_operations = a.clone();
508 let mut simple_operations = a;
509 complex_operations.retain(|_, v| v.is_task()); simple_operations.retain(|_, v| !v.is_task()); if target_task.is_some() {
512 let tt_name = target_task.clone().unwrap();
513 simple_operations.retain(|_, v| v.name == tt_name);
517 }
518 for (_name, _mo) in simple_operations.into_iter() {
519 let p = _mo.post.clone();
521 let a = perform_operation_on(_mo).map_err(|e| {
522 hmerror::error(
523 format!("Failed to perform operation on {:#?}", _name).as_str(),
524 e.to_string().as_str(),
525 )
526 });
527 if a.is_ok() {
528 hmerror::happy_print(format!("Successfully performed operation on {:#?}", _name).as_str());
529 if !p.is_empty() {
530 println!("↳ Executing post {} for {}... ", p, _name);
531 let _ = execute_solution(p);
532 }
533 }
534 }
535 let (tx, rx) = mpsc::channel();
536 let mp: MultiProgress = MultiProgress::new();
537 let mut t: HashSet<String> = HashSet::new();
538 let _v = get_task_batches(complex_operations, target_task).unwrap_or_else(|er| {
539 hmerror::error(
540 "Error occurred attempting to get task batches",
541 format!("{}{}", "\n", er.to_string().as_str()).as_str(),
542 );
543 exit(3);
544 });
545 for _a in _v {
546 for _b in _a {
547 t.insert(_b.name.to_string());
548 let _p: ProgressBar = mp.add(ProgressBar::new_spinner());
549 send_tasks_off_to_college(&_b, &tx, _p).expect("ohtehnoes");
550 }
551 }
552
553 let mut v: HashMap<String, config::Worker> = HashMap::new();
554
555 loop {
558 if let Ok(_t) = rx.try_recv() {
559 v.insert(_t.name.clone(), _t.clone());
560 }
561 std::thread::sleep(time::Duration::from_millis(10));
562 if !all_workers_done(&v) {
563 continue;
564 }
565 break;
566 }
567 mp.join().unwrap();
568 Ok(())
569}
570
571fn all_workers_done(workers: &HashMap<String, config::Worker>) -> bool {
576 for w in workers.values() {
577 if !w.completed {
578 return false;
579 }
580 }
581 true
582}