#[cfg(feature = "tui")]
use crate::tui::app::TuiApp;
#[cfg(feature = "tui")]
use crossterm::{
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
execute,
terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
};
#[cfg(feature = "tui")]
use ratatui::{
prelude::*,
widgets::{Block, Borders, Gauge, Paragraph},
};
#[cfg(feature = "tui")]
use std::io;
#[cfg(feature = "tui")]
use std::sync::{Arc, Mutex};
#[cfg(feature = "tui")]
use std::time::Duration;
#[cfg(feature = "tui")]
pub fn init_terminal() -> io::Result<Terminal<CrosstermBackend<std::io::Stdout>>> {
let mut stdout = io::stdout();
enable_raw_mode()?;
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
Terminal::new(CrosstermBackend::new(stdout))
}
#[cfg(feature = "tui")]
pub fn restore_terminal(
terminal: &mut Terminal<CrosstermBackend<std::io::Stdout>>,
) -> io::Result<()> {
disable_raw_mode()?;
execute!(
terminal.backend_mut(),
LeaveAlternateScreen,
DisableMouseCapture
)?;
terminal.show_cursor()?;
Ok(())
}
#[cfg(feature = "tui")]
pub fn handle_input() -> io::Result<bool> {
if event::poll(Duration::from_millis(100))? {
if let Event::Key(key) = event::read()? {
if key.code == KeyCode::Char('q') || key.code == KeyCode::Esc {
return Ok(false);
}
}
}
Ok(true)
}
#[cfg(feature = "tui")]
pub fn draw_ui(f: &mut Frame, app: &TuiApp) {
let chunks = Layout::default()
.direction(Direction::Vertical)
.margin(2)
.constraints(
[
Constraint::Length(3),
Constraint::Length(3),
Constraint::Length(3),
Constraint::Length(3),
Constraint::Min(0),
]
.as_ref(),
)
.split(f.area());
let title = Paragraph::new("SQL Log Export Monitor")
.style(Style::default().fg(Color::Cyan).bold())
.alignment(Alignment::Center);
f.render_widget(title, chunks[0]);
let progress_pct = app.progress_percent();
let progress = Gauge::default()
.block(Block::default().title("Progress").borders(Borders::ALL))
.gauge_style(Style::default().fg(Color::Green).bold())
.percent(progress_pct)
.label(format!(
"{}/{} files",
app.current_file_index, app.total_files
));
f.render_widget(progress, chunks[1]);
let file_info = Paragraph::new(format!(
"Current File: {}\nExporter: {}",
app.current_file_name, app.exporter_name
))
.block(Block::default().title("File Info").borders(Borders::ALL))
.style(Style::default().fg(Color::White));
f.render_widget(file_info, chunks[2]);
let elapsed = app.elapsed_secs();
let throughput = app.throughput();
let stats = Paragraph::new(format!(
"Records: {}\nErrors: {}\nElapsed: {:.0}s\nThroughput: {:.0} rec/s",
app.exported_records, app.error_records, elapsed, throughput
))
.block(Block::default().title("Statistics").borders(Borders::ALL))
.style(Style::default().fg(Color::Yellow));
f.render_widget(stats, chunks[3]);
}
#[cfg(feature = "tui")]
pub async fn run_tui(app_state: Arc<Mutex<TuiApp>>) -> io::Result<()> {
let mut terminal = init_terminal()?;
terminal.clear()?;
loop {
let app = app_state.lock().unwrap().clone();
terminal.draw(|f| draw_ui(f, &app))?;
if !handle_input()? || app.is_finished {
break;
}
tokio::time::sleep(Duration::from_millis(50)).await;
}
restore_terminal(&mut terminal)?;
Ok(())
}