added auto update & changed config path
This commit is contained in:
124
src/app.rs
124
src/app.rs
@@ -4,6 +4,7 @@ use crate::error::Result;
|
||||
use crate::executor;
|
||||
use crate::menu::{Action, InteractionMode, MenuItem, MenuItemKind, MenuRoot};
|
||||
use crate::network;
|
||||
use crate::updater;
|
||||
use crossterm::event::{Event as CrosstermEvent, KeyCode, KeyEventKind};
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
|
||||
@@ -16,6 +17,10 @@ pub enum Event {
|
||||
StructuredCommand(executor::StructuredCommand),
|
||||
StructuredOutput(String),
|
||||
StructuredFinished(i32),
|
||||
UpdateAvailable(updater::UpdateInfo),
|
||||
UpdateProgress(u64),
|
||||
UpdateDone,
|
||||
UpdateError(String),
|
||||
}
|
||||
|
||||
/// A pending request to hand the terminal to an external interactive process.
|
||||
@@ -35,6 +40,7 @@ pub struct App {
|
||||
pub event_tx: UnboundedSender<Event>,
|
||||
/// When set, main loop suspends ratatui, runs the command, then restores.
|
||||
pub pending_exec: Option<PendingExec>,
|
||||
pub pending_restart: bool,
|
||||
}
|
||||
|
||||
pub enum Popup {
|
||||
@@ -62,6 +68,22 @@ pub enum Popup {
|
||||
},
|
||||
Downloading { progress: f32, message: String },
|
||||
Message { text: String, level: MessageLevel },
|
||||
UpdateConfirm {
|
||||
info: updater::UpdateInfo,
|
||||
},
|
||||
Updating {
|
||||
info: updater::UpdateInfo,
|
||||
downloaded: u64,
|
||||
status: UpdatingStatus,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum UpdatingStatus {
|
||||
Downloading,
|
||||
Applying,
|
||||
Done,
|
||||
Failed(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
@@ -82,9 +104,25 @@ impl App {
|
||||
popup: None,
|
||||
event_tx,
|
||||
pending_exec: None,
|
||||
pending_restart: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_update(&mut self) {
|
||||
let api_base = match &self.config.update_api {
|
||||
Some(url) => url.clone(),
|
||||
None => return,
|
||||
};
|
||||
let timeout = self.config.timeout_sec;
|
||||
let tx = self.event_tx.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
if let Ok(Some(info)) = updater::check(&api_base, timeout).await {
|
||||
let _ = tx.send(Event::UpdateAvailable(info));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub async fn load_menu(&mut self) {
|
||||
let server_url = self.config.server_url.clone();
|
||||
let timeout = self.config.timeout_sec;
|
||||
@@ -190,6 +228,32 @@ impl App {
|
||||
// script), we silently discard the exit event.
|
||||
Ok(false)
|
||||
}
|
||||
Event::UpdateAvailable(info) => {
|
||||
// Only show if no popup is currently open (don't interrupt running scripts)
|
||||
if self.popup.is_none() {
|
||||
self.popup = Some(Popup::UpdateConfirm { info });
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
Event::UpdateProgress(bytes) => {
|
||||
if let Some(Popup::Updating { downloaded, .. }) = &mut self.popup {
|
||||
*downloaded = bytes;
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
Event::UpdateDone => {
|
||||
if let Some(Popup::Updating { status, .. }) = &mut self.popup {
|
||||
*status = UpdatingStatus::Done;
|
||||
}
|
||||
self.pending_restart = true;
|
||||
Ok(false)
|
||||
}
|
||||
Event::UpdateError(msg) => {
|
||||
if let Some(Popup::Updating { status, .. }) = &mut self.popup {
|
||||
*status = UpdatingStatus::Failed(msg);
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,6 +411,66 @@ impl App {
|
||||
self.popup = None;
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
Popup::UpdateConfirm { info } => {
|
||||
match key.code {
|
||||
KeyCode::Char('y') | KeyCode::Char('Y') => {
|
||||
let info = info.clone();
|
||||
self.popup = Some(Popup::Updating {
|
||||
info: info.clone(),
|
||||
downloaded: 0,
|
||||
status: UpdatingStatus::Downloading,
|
||||
});
|
||||
|
||||
let (progress_tx, mut progress_rx) =
|
||||
tokio::sync::mpsc::unbounded_channel::<u64>();
|
||||
let tx = self.event_tx.clone();
|
||||
let info_clone = info.clone();
|
||||
|
||||
// Forward progress events
|
||||
tokio::spawn(async move {
|
||||
while let Some(bytes) = progress_rx.recv().await {
|
||||
let _ = tx.send(Event::UpdateProgress(bytes));
|
||||
}
|
||||
});
|
||||
|
||||
// Download and apply
|
||||
let tx2 = self.event_tx.clone();
|
||||
tokio::spawn(async move {
|
||||
match updater::download_and_apply(
|
||||
&info_clone,
|
||||
progress_tx,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(()) => {
|
||||
let _ = tx2.send(Event::UpdateDone);
|
||||
}
|
||||
Err(e) => {
|
||||
let _ =
|
||||
tx2.send(Event::UpdateError(e.to_string()));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
KeyCode::Char('n') | KeyCode::Char('N') | KeyCode::Esc => {
|
||||
self.popup = None;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
Popup::Updating { status, .. } => {
|
||||
if key.code == KeyCode::Esc {
|
||||
if matches!(status, UpdatingStatus::Failed(_)) {
|
||||
self.popup = None;
|
||||
}
|
||||
// Ignore Esc while downloading/applying
|
||||
}
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user