mod_installer/
weidu_parser.rs

1use std::{
2    sync::{
3        Arc,
4        atomic::{AtomicUsize, Ordering},
5        mpsc::{Receiver, Sender, TryRecvError},
6    },
7    thread,
8};
9
10use config::{args::Options, parser_config::ParserConfig, state::State};
11
12use crate::utils::sleep;
13
14#[derive(Debug)]
15enum ParserState {
16    CollectingQuestion,
17    WaitingForMoreQuestionContent,
18    LookingForInterestingOutput,
19}
20
21pub(crate) fn parse_raw_output(
22    options: &Options,
23    sender: Sender<State>,
24    receiver: Receiver<String>,
25    parser_config: Arc<ParserConfig>,
26    wait_count: Arc<AtomicUsize>,
27) {
28    let mut current_state = ParserState::LookingForInterestingOutput;
29    let mut buffer = vec![];
30    let mut question = vec![];
31    let mut grace_ticks: usize = 3;
32    sender
33        .send(State::InProgress)
34        .expect("Failed to send process start event");
35    let options = options.clone();
36    thread::spawn(move || {
37        loop {
38            match receiver.try_recv() {
39                Ok(string) => {
40                    log::info!("{string}");
41                    let installer_state = parser_config.detect_weidu_finished_state(&string);
42                    if installer_state != State::InProgress {
43                        sender
44                            .send(installer_state)
45                            .expect("Failed to send process error event");
46                        break;
47                    }
48                    buffer.push(string.clone());
49                    match current_state {
50                        ParserState::CollectingQuestion
51                        | ParserState::WaitingForMoreQuestionContent => {
52                            if parser_config.useful_status_words.contains(&string) {
53                                log::debug!(
54                                    "Weidu seems to know an answer for the last question, ignoring it"
55                                );
56                                current_state = ParserState::LookingForInterestingOutput;
57                                question.clear();
58                            } else {
59                                log::debug!("Appending line '{string}' to user question");
60                                question.push(string);
61                                current_state = ParserState::CollectingQuestion;
62                            }
63                        }
64                        ParserState::LookingForInterestingOutput => {
65                            if parser_config.string_looks_like_question(&string) {
66                                log::debug!(
67                                    "Changing parser state to '{:?}' due to line {}",
68                                    ParserState::CollectingQuestion,
69                                    string
70                                );
71                                current_state = ParserState::CollectingQuestion;
72                                let min_index = buffer.len().saturating_sub(options.lookback);
73                                for history in buffer.get(min_index..).unwrap_or_default() {
74                                    question.push(history.clone());
75                                }
76                            }
77                        }
78                    }
79                }
80                Err(TryRecvError::Empty) => match current_state {
81                    ParserState::CollectingQuestion if grace_ticks > 0 => {
82                        log::debug!("Collecting question, with grace of {grace_ticks} remaining");
83                        sleep(options.tick);
84                        grace_ticks -= 1;
85                    }
86                    ParserState::CollectingQuestion => {
87                        log::debug!(
88                            "Changing parser state to '{:?}'",
89                            ParserState::WaitingForMoreQuestionContent
90                        );
91                        current_state = ParserState::WaitingForMoreQuestionContent;
92                        grace_ticks = 3;
93                    }
94                    ParserState::WaitingForMoreQuestionContent => {
95                        log::debug!("No new weidu output, sending question to user");
96                        sender
97                            .send(State::RequiresInput {
98                                question: question.join(""),
99                            })
100                            .expect("Failed to send question");
101                        current_state = ParserState::LookingForInterestingOutput;
102                        question.clear();
103                        continue;
104                    }
105                    _ if wait_count.load(Ordering::Relaxed) >= options.timeout => {
106                        sender
107                            .send(State::TimedOut)
108                            .expect("Could send timeout error");
109                    }
110                    _ => {}
111                },
112                Err(TryRecvError::Disconnected) => {
113                    sender
114                        .send(State::Completed)
115                        .expect("Failed to send process end event");
116                    break;
117                }
118            }
119        }
120    });
121}