use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, mpsc};
use std::thread;
use std::time::{Duration, Instant};
use anyhow::Result;
use crossterm::event::{self, Event as CrosstermEvent, KeyEvent, KeyEventKind};
pub enum AppEvent {
Key(KeyEvent),
Tick,
PingResult {
alias: String,
rtt_ms: Option<u32>,
generation: u64,
},
SyncComplete {
provider: String,
hosts: Vec<crate::providers::ProviderHost>,
},
SyncPartial {
provider: String,
hosts: Vec<crate::providers::ProviderHost>,
failures: usize,
total: usize,
},
SyncError {
provider: String,
message: String,
},
SyncProgress {
provider: String,
message: String,
},
UpdateAvailable {
version: String,
headline: Option<String>,
},
FileBrowserListing {
alias: String,
path: String,
entries: Result<Vec<crate::file_browser::FileEntry>, String>,
},
ScpComplete {
alias: String,
success: bool,
message: String,
},
SnippetHostDone {
run_id: u64,
alias: String,
stdout: String,
stderr: String,
exit_code: Option<i32>,
},
SnippetAllDone {
run_id: u64,
},
SnippetProgress {
run_id: u64,
completed: usize,
total: usize,
},
ContainerListing {
alias: String,
result: Result<
(
crate::containers::ContainerRuntime,
Vec<crate::containers::ContainerInfo>,
),
crate::containers::ContainerError,
>,
},
ContainerActionComplete {
alias: String,
action: crate::containers::ContainerAction,
result: Result<(), String>,
},
VaultSignResult {
alias: String,
certificate_file: String,
success: bool,
message: String,
},
VaultSignProgress {
alias: String,
done: usize,
total: usize,
},
VaultSignAllDone {
signed: u32,
failed: u32,
skipped: u32,
cancelled: bool,
aborted_message: Option<String>,
first_error: Option<String>,
},
CertCheckResult {
alias: String,
status: crate::vault_ssh::CertStatus,
},
CertCheckError {
alias: String,
message: String,
},
PollError,
}
pub struct EventHandler {
tx: mpsc::Sender<AppEvent>,
rx: mpsc::Receiver<AppEvent>,
paused: Arc<AtomicBool>,
_handle: thread::JoinHandle<()>,
}
impl EventHandler {
pub fn new(tick_rate_ms: u64) -> Self {
let (tx, rx) = mpsc::channel();
let tick_rate = Duration::from_millis(tick_rate_ms);
let event_tx = tx.clone();
let paused = Arc::new(AtomicBool::new(false));
let paused_flag = paused.clone();
let handle = thread::spawn(move || {
let mut last_tick = Instant::now();
loop {
if paused_flag.load(Ordering::Acquire) {
thread::sleep(Duration::from_millis(50));
continue;
}
let remaining = tick_rate
.checked_sub(last_tick.elapsed())
.unwrap_or(Duration::ZERO);
let timeout = remaining.min(Duration::from_millis(50));
match event::poll(timeout) {
Ok(true) => {
if let Ok(evt) = event::read() {
match evt {
CrosstermEvent::Key(key) if key.kind == KeyEventKind::Press => {
if event_tx.send(AppEvent::Key(key)).is_err() {
return;
}
}
CrosstermEvent::Resize(..) => {
if event_tx.send(AppEvent::Tick).is_err() {
return;
}
}
_ => {}
}
}
}
Ok(false) => {}
Err(_) => {
let _ = event_tx.send(AppEvent::PollError);
return;
}
}
if last_tick.elapsed() >= tick_rate {
if event_tx.send(AppEvent::Tick).is_err() {
return;
}
last_tick = Instant::now();
}
}
});
Self {
tx,
rx,
paused,
_handle: handle,
}
}
pub fn next(&self) -> Result<AppEvent> {
Ok(self.rx.recv()?)
}
pub fn next_timeout(&self, timeout: Duration) -> Result<Option<AppEvent>> {
match self.rx.recv_timeout(timeout) {
Ok(event) => Ok(Some(event)),
Err(mpsc::RecvTimeoutError::Timeout) => Ok(None),
Err(mpsc::RecvTimeoutError::Disconnected) => {
Err(anyhow::anyhow!("event channel disconnected"))
}
}
}
pub fn sender(&self) -> mpsc::Sender<AppEvent> {
self.tx.clone()
}
pub fn pause(&self) {
self.paused.store(true, Ordering::Release);
}
pub fn resume(&self) {
let mut preserved = Vec::new();
while let Ok(event) = self.rx.try_recv() {
match event {
AppEvent::PingResult { .. }
| AppEvent::SyncComplete { .. }
| AppEvent::SyncPartial { .. }
| AppEvent::SyncError { .. }
| AppEvent::SyncProgress { .. }
| AppEvent::UpdateAvailable { .. }
| AppEvent::FileBrowserListing { .. }
| AppEvent::ScpComplete { .. }
| AppEvent::SnippetHostDone { .. }
| AppEvent::SnippetAllDone { .. }
| AppEvent::SnippetProgress { .. }
| AppEvent::ContainerListing { .. }
| AppEvent::ContainerActionComplete { .. }
| AppEvent::VaultSignResult { .. }
| AppEvent::VaultSignProgress { .. }
| AppEvent::VaultSignAllDone { .. }
| AppEvent::CertCheckResult { .. }
| AppEvent::CertCheckError { .. } => preserved.push(event),
_ => {}
}
}
for event in preserved {
let _ = self.tx.send(event);
}
self.paused.store(false, Ordering::Release);
}
}