Skip to content

Commit

Permalink
Merge pull request #30 from dark0dave/refactor/cleanup
Browse files Browse the repository at this point in the history
Cleanup
  • Loading branch information
dark0dave authored Nov 20, 2023
2 parents 1db0894 + 432e71e commit 78066b0
Show file tree
Hide file tree
Showing 7 changed files with 207 additions and 182 deletions.
29 changes: 20 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,26 @@ Usage: mod_installer [OPTIONS] --log-file <LOG_FILE> \
--mod-directories <MOD_DIRECTORIES>

Options:
--log-file <LOG_FILE> Full path to target log [env: LOG_FILE]
-g, --game-directory <GAME_DIRECTORY> Full path to game directory [env: GAME_DIRECTORY]
-w, --weidu-binary <WEIDU_BINARY> Full Path to weidu binary [env: WEIDU_BINARY]
-m, --mod-directories <MOD_DIRECTORIES> Full Path to mod directories [env: MOD_DIRECTORIES]
-l, --language <LANGUAGE> Game Language [default: en_US]
-d, --depth <DEPTH> Depth to walk folder structure [default: 3]
-s, --skip-installed Compare against installed weidu log, note this is best effort
-h, --help Print help
-V, --version Print version
--log-file <LOG_FILE>
Full path to target log [env: LOG_FILE=]
-g, --game-directory <GAME_DIRECTORY>
Full path to game directory [env: GAME_DIRECTORY=]
-w, --weidu-binary <WEIDU_BINARY>
Full Path to weidu binary [env: WEIDU_BINARY=]
-m, --mod-directories <MOD_DIRECTORIES>
Full Path to mod directories [env: MOD_DIRECTORIES=]
-l, --language <LANGUAGE>
Game Language [default: en_US]
-d, --depth <DEPTH>
Depth to walk folder structure [default: 3]
-s, --skip-installed
Compare against installed weidu log, note this is best effort
-a, --abort-on-warnings
If a warning occurs in the weidu child process exit
-h, --help
Print help
-V, --version
Print version
```

## Log levels
Expand Down
5 changes: 3 additions & 2 deletions src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ pub struct Args {
#[clap(long, short, action=ArgAction::SetTrue)]
pub skip_installed: bool,

#[clap(long, action=ArgAction::SetTrue)]
pub stop_on_warnings: bool,
/// If a warning occurs in the weidu child process exit
#[clap(long, short, action=ArgAction::SetTrue)]
pub abort_on_warnings: bool,
}

fn parse_absolute_path(arg: &str) -> Result<PathBuf, String> {
Expand Down
10 changes: 6 additions & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ use crate::{

mod args;
mod mod_component;
mod state;
mod utils;
mod weidu;
mod weidu_parser;

fn main() {
env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
println!(
log::info!(
r"
/\/\ ___ __| | (_)_ __ ___| |_ __ _| | | ___ _ __
/ \ / _ \ / _` | | | '_ \/ __| __/ _` | | |/ _ \ '__|
Expand Down Expand Up @@ -90,11 +92,11 @@ fn main() {
log::info!("Installed mod {:?}", &weidu_mod);
}
InstallationResult::Warnings => {
if args.stop_on_warnings {
log::info!("Installed mod {:?} with warnings, stopping", &weidu_mod);
if args.abort_on_warnings {
log::error!("Installed mod {:?} with warnings, stopping", &weidu_mod);
break;
} else {
log::info!("Installed mod {:?} with warnings, keep going", &weidu_mod);
log::warn!("Installed mod {:?} with warnings, keep going", &weidu_mod);
}
}
}
Expand Down
8 changes: 8 additions & 0 deletions src/state.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#[derive(Debug)]
pub enum State {
RequiresInput { question: String },
InProgress,
Completed,
CompletedWithErrors { error_details: String },
CompletedWithWarnings,
}
7 changes: 7 additions & 0 deletions src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use core::time;
use fs_extra::dir::{copy, CopyOptions};
use std::{
fs::File,
path::{Path, PathBuf},
thread,
};
use walkdir::WalkDir;

Expand Down Expand Up @@ -70,6 +72,11 @@ fn find_mod_folder(mod_component: &ModComponent, mod_dir: &Path, depth: usize) -
})
}

pub fn sleep(millis: u64) {
let duration = time::Duration::from_millis(millis);
thread::sleep(duration);
}

#[cfg(test)]
mod tests {

Expand Down
187 changes: 20 additions & 167 deletions src/weidu.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
use core::time;
use std::{
io::{self, BufRead, BufReader, ErrorKind, Write},
panic,
path::PathBuf,
process::{Child, ChildStdout, Command, Stdio},
sync::mpsc::{self, Receiver, Sender, TryRecvError},
sync::mpsc::{self, Receiver, TryRecvError},
thread,
};

use crate::mod_component::ModComponent;
use crate::{
mod_component::ModComponent, state::State, utils::sleep, weidu_parser::parse_raw_output,
};

pub fn get_user_input() -> String {
let stdin = io::stdin();
Expand Down Expand Up @@ -56,48 +57,40 @@ pub fn install(
handle_io(child)
}

#[derive(Debug)]
enum ProcessStateChange {
RequiresInput { question: String },
InProgress,
Completed,
CompletedWithErrors { error_details: String },
CompletedWithWarnings,
}

pub fn handle_io(mut child: Child) -> InstallationResult {
let mut weidu_stdin = child.stdin.take().unwrap();
let output_lines_receiver = create_output_reader(child.stdout.take().unwrap());
let parsed_output_receiver = create_parsed_output_receiver(output_lines_receiver);
let raw_output_receiver = create_output_reader(child.stdout.take().unwrap());
let (sender, parsed_output_receiver) = mpsc::channel::<State>();
parse_raw_output(sender, raw_output_receiver);

let mut wait_counter = 0;
loop {
match parsed_output_receiver.try_recv() {
Ok(state) => {
log::debug!("Current installer state is {:?}", state);
match state {
ProcessStateChange::Completed => {
State::Completed => {
log::debug!("Weidu process completed");
break;
}
ProcessStateChange::CompletedWithErrors { error_details } => {
log::debug!("Weidu process seem to have completed with errors");
State::CompletedWithErrors { error_details } => {
log::error!("Weidu process seem to have completed with errors");
weidu_stdin
.write_all("\n".as_bytes())
.expect("Failed to send final ENTER to weidu process");
return InstallationResult::Fail(error_details);
}
ProcessStateChange::CompletedWithWarnings => {
log::debug!("Weidu process seem to have completed with warnings");
State::CompletedWithWarnings => {
log::warn!("Weidu process seem to have completed with warnings");
weidu_stdin
.write_all("\n".as_bytes())
.expect("Failed to send final ENTER to weidu process");
return InstallationResult::Warnings;
}
ProcessStateChange::InProgress => {
State::InProgress => {
log::debug!("In progress...");
}
ProcessStateChange::RequiresInput { question } => {
State::RequiresInput { question } => {
log::info!("User Input required");
log::info!("Question is");
log::info!("{}\n", question);
Expand All @@ -110,15 +103,17 @@ pub fn handle_io(mut child: Child) -> InstallationResult {
}
}
Err(TryRecvError::Empty) => {
print!("Waiting for child process to end");
print!("{}\r", ".".repeat(wait_counter));
log::info!("Waiting for child process to end");
log::info!("{}\r", ".".repeat(wait_counter));
std::io::stdout().flush().expect("Failed to flush stdout");

wait_counter += 1;
wait_counter %= 10;
sleep(500);

print!("\r \r");
log::info!(
"\r \r"
);
std::io::stdout().flush().expect("Failed to flush stdout");
}
Err(TryRecvError::Disconnected) => break,
Expand All @@ -127,143 +122,6 @@ pub fn handle_io(mut child: Child) -> InstallationResult {
InstallationResult::Success
}

#[derive(Debug)]
enum ParserState {
CollectingQuestion,
WaitingForMoreQuestionContent,
LookingForInterestingOutput,
}

fn create_parsed_output_receiver(
raw_output_receiver: Receiver<String>,
) -> Receiver<ProcessStateChange> {
let (sender, receiver) = mpsc::channel::<ProcessStateChange>();
parse_raw_output(sender, raw_output_receiver);
receiver
}

fn parse_raw_output(sender: Sender<ProcessStateChange>, receiver: Receiver<String>) {
let mut current_state = ParserState::LookingForInterestingOutput;
let mut question = String::new();
sender
.send(ProcessStateChange::InProgress)
.expect("Failed to send process start event");
thread::spawn(move || loop {
match receiver.try_recv() {
Ok(string) => match current_state {
ParserState::CollectingQuestion | ParserState::WaitingForMoreQuestionContent => {
if string_looks_like_weidu_is_doing_something_useful(&string) {
log::debug!(
"Weidu seems to know an answer for the last question, ignoring it"
);
current_state = ParserState::LookingForInterestingOutput;
question.clear();
} else {
log::debug!("Appending line '{}' to user question", string);
question.push_str(string.as_str());
current_state = ParserState::CollectingQuestion;
}
}
ParserState::LookingForInterestingOutput => {
let may_be_weidu_finished_state = detect_weidu_finished_state(&string);
if let Some(weidu_finished_state) = may_be_weidu_finished_state {
sender
.send(weidu_finished_state)
.expect("Failed to send process error event");
break;
} else if string_looks_like_question(&string) {
log::debug!(
"Changing parser state to '{:?}' due to line {}",
ParserState::CollectingQuestion,
string
);
current_state = ParserState::CollectingQuestion;
question.push_str(string.as_str());
} else {
log::debug!("Ignoring line {}", string);
}
}
},
Err(TryRecvError::Empty) => {
match current_state {
ParserState::CollectingQuestion => {
log::debug!(
"Changing parser state to '{:?}'",
ParserState::WaitingForMoreQuestionContent
);
current_state = ParserState::WaitingForMoreQuestionContent;
}
ParserState::WaitingForMoreQuestionContent => {
log::debug!("No new weidu otput, sending question to user");
sender
.send(ProcessStateChange::RequiresInput { question })
.expect("Failed to send question");
current_state = ParserState::LookingForInterestingOutput;
question = String::new();
}
_ => {
// there is no new weidu output and we are not waiting for any, so there is nothing to do
}
}
sleep(100);
}
Err(TryRecvError::Disconnected) => {
sender
.send(ProcessStateChange::Completed)
.expect("Failed to send provess end event");
break;
}
}
});
}

fn detect_weidu_finished_state(string: &str) -> Option<ProcessStateChange> {
if string_looks_like_weidu_completed_with_errors(string) {
Some(ProcessStateChange::CompletedWithErrors {
error_details: string.trim().to_string(),
})
} else if string_looks_like_weidu_completed_with_warnings(string) {
Some(ProcessStateChange::CompletedWithWarnings)
} else {
None
}
}

fn string_looks_like_question(string: &str) -> bool {
let lowercase_string = string.trim().to_lowercase();
!lowercase_string.contains("installing")
&& (lowercase_string.contains("choice")
|| lowercase_string.starts_with("choose")
|| lowercase_string.starts_with("select")
|| lowercase_string.starts_with("do you want")
|| lowercase_string.starts_with("would you like")
|| lowercase_string.starts_with("enter"))
|| lowercase_string.ends_with('?')
|| lowercase_string.ends_with(':')
}

fn string_looks_like_weidu_is_doing_something_useful(string: &str) -> bool {
let lowercase_string = string.trim().to_lowercase();
lowercase_string.contains("copying")
|| lowercase_string.contains("copied")
|| lowercase_string.contains("installing")
|| lowercase_string.contains("installed")
|| lowercase_string.contains("patching")
|| lowercase_string.contains("patched")
|| lowercase_string.contains("processing")
|| lowercase_string.contains("processed")
}

fn string_looks_like_weidu_completed_with_errors(string: &str) -> bool {
let lowercase_string = string.trim().to_lowercase();
lowercase_string.contains("not installed due to errors")
}

fn string_looks_like_weidu_completed_with_warnings(string: &str) -> bool {
let lowercase_string = string.trim().to_lowercase();
lowercase_string.contains("installed with warnings")
}

fn create_output_reader(out: ChildStdout) -> Receiver<String> {
let (tx, rx) = mpsc::channel::<String>();
let mut buffered_reader = BufReader::new(out);
Expand All @@ -275,7 +133,7 @@ fn create_output_reader(out: ChildStdout) -> Receiver<String> {
break;
}
Ok(_) => {
log::debug!("Got line from process: '{}'", line);
log::debug!("{}", line);
tx.send(line).expect("Failed to sent process output line");
}
Err(ref e) if e.kind() == ErrorKind::InvalidData => {
Expand All @@ -291,8 +149,3 @@ fn create_output_reader(out: ChildStdout) -> Receiver<String> {
});
rx
}

fn sleep(millis: u64) {
let duration = time::Duration::from_millis(millis);
thread::sleep(duration);
}
Loading

0 comments on commit 78066b0

Please sign in to comment.