Files
avam/avam-client/src/state_machine.rs
2024-10-17 12:49:56 +02:00

159 lines
5.0 KiB
Rust

use tokio::sync::broadcast::{Receiver, Sender};
use crate::{
config::Config,
models::{CodeChallengeMethod, CodeVerifier},
oauth,
};
#[derive(Debug, PartialEq)]
pub enum State {
Init,
AppStart {
config: Config,
},
Authenticate {
open_browser: bool,
code_verifier: CodeVerifier,
code_challenge_method: CodeChallengeMethod,
},
Connect {
token: String,
},
WaitForSim,
Running,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Event {
Ready {
config: Config,
},
StartAuthenticate {
open_browser: bool,
code_verifier: CodeVerifier,
code_challenge_method: CodeChallengeMethod,
}, // should not be string
TokenReceived {
token: String,
}, // AppStart and Authenticate can fire off TokenReceived to transition into Connect
Connected, // Once connected to the socket, and properly authenticated, fire off Connected to transition to WaitForSim
Disconnected, // If for whatever reason we're disconnected from the backend, we need to transition back to Connect
SimConnected, // SimConnect is connected, we're in the world and ready to send data, transition to Running
SimDisconnected, // SimConnect is disconnected, we've finished the flight and exited back to the menu, transition back to WaitForSim
Quit,
}
impl State {
pub async fn next(self, event: Event) -> State {
match (self, event) {
// (Current State, SomeEvent) => NextState
(_, Event::Ready { config }) => State::AppStart { config },
(
State::AppStart { .. },
Event::StartAuthenticate {
open_browser,
code_verifier,
code_challenge_method,
},
) => Self::Authenticate {
open_browser,
code_verifier,
code_challenge_method,
}, // Goto Authenticate
(State::AppStart { .. }, Event::TokenReceived { token }) => State::Connect { token },
(State::Authenticate { .. }, Event::TokenReceived { token }) => {
State::Connect { token }
}
(State::Connect { .. }, Event::Connected) => todo!(), // Goto WaitForSim
(State::WaitForSim, Event::SimConnected) => todo!(), // Goto Running
(State::Running, Event::Disconnected) => todo!(), // Goto Connect
(State::Running, Event::SimDisconnected) => todo!(), // Goto WaitForSim
(_, Event::Quit) => todo!(), // All events can go into quit, to shutdown the application
_ => panic!("Invalid state transition"),
}
}
pub async fn run(&self, signal: Sender<Event>) -> Result<(), anyhow::Error> {
match self {
State::Init => Ok(()),
State::AppStart { config } => {
if let Some(token) = config.token() {
signal.send(Event::TokenReceived {
token: token.to_string(),
})?;
} else {
let open_browser = config.open_browser();
let code_verifier = CodeVerifier::new();
let code_challenge_method = CodeChallengeMethod::Sha256;
config.set_code_verifier(Some(code_verifier.clone()))?;
config.set_code_challenge_method(Some(code_challenge_method.clone()))?;
signal.send(Event::StartAuthenticate {
open_browser,
code_verifier,
code_challenge_method,
})?;
}
Ok(())
}
State::Authenticate {
open_browser,
code_verifier,
code_challenge_method,
} => {
if *open_browser {
oauth::open_browser(code_verifier.clone(), code_challenge_method.clone())?;
}
Ok(())
}
State::Connect { token } => {
println!("Holyshit we've got a token: {}", token);
Ok(())
}
State::WaitForSim => Ok(()),
State::Running => Ok(()),
}
}
}
pub async fn start(
config: Config,
event_sender: Sender<Event>,
mut event_receiver: Receiver<Event>,
) -> Result<(), anyhow::Error> {
let mut state = State::Init;
state.run(event_sender.clone()).await?;
loop {
if let Ok(event) = event_receiver.recv().await {
if event == Event::Quit {
tracing::info!("Shutting down State Machine");
break;
}
state = state.next(event).await;
// before run
if let State::Connect { token } = &state {
// before run Connect, save the given token in config
config.set_token(Some(token.clone()))?;
}
state.run(event_sender.clone()).await?;
}
}
tracing::info!("State Machine Shutdown");
Ok(())
}