diff --git a/Cargo.lock b/Cargo.lock index de957d6..b124963 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1123,7 +1123,7 @@ dependencies = [ [[package]] name = "ostiary" -version = "1.0.5" +version = "1.0.7" dependencies = [ "anyhow", "crossterm", diff --git a/Cargo.toml b/Cargo.toml index cf17369..79622c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ostiary" -version = "1.0.6" +version = "1.0.7" edition = "2024" [dependencies] diff --git a/src/app.rs b/src/app.rs index 7a0e2cf..d5bb320 100644 --- a/src/app.rs +++ b/src/app.rs @@ -517,6 +517,12 @@ impl App { .await { Ok(exe_path) => { + // Write expected version before exec so + // the next startup can detect a failed + // replacement (wrong asset, etc.). + updater::write_update_target( + &info_clone.new_version, + ); let _ = tx2.send(Event::UpdateDone(exe_path)); } Err(e) => { diff --git a/src/main.rs b/src/main.rs index 0798687..028c823 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,8 +35,26 @@ async fn main() -> Result<()> { let (event_tx, mut event_rx) = mpsc::unbounded_channel(); let mut app = app::App::new(config, event_tx.clone()); + + // Detect failed update: if the previous run wrote an expected version but + // the actual version didn't change, the replacement didn't work. + // Show an error and skip the update check to break any infinite loop. + let update_ok = match updater::take_update_target() { + Some(expected) if expected != env!("CARGO_PKG_VERSION") => { + app.error = Some(format!( + "Обновление не применилось: ожидалась v{expected}, \ + запущена v{}. Проверьте, что в релизе загружен правильный бинарник.", + env!("CARGO_PKG_VERSION") + )); + false + } + _ => true, + }; + app.load_menu().await; - app.check_update(); + if update_ok { + app.check_update(); + } let res = run_app(&mut terminal, &mut app, &mut event_rx).await; diff --git a/src/ui.rs b/src/ui.rs index 94ebd3a..94bc391 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -48,7 +48,18 @@ fn render_menu(f: &mut Frame, app: &App, area: Rect) { .collect(); let list = List::new(list_items) - .block(Block::default().borders(Borders::ALL).title("Меню")) + .block( + Block::default() + .borders(Borders::ALL) + .title(" Меню ") + .title( + Line::from(Span::styled( + format!(" v{} ", env!("CARGO_PKG_VERSION")), + Style::default().fg(Color::DarkGray), + )) + .alignment(Alignment::Right), + ), + ) .highlight_style(Style::default().add_modifier(Modifier::BOLD)); f.render_widget(list, area); } diff --git a/src/updater.rs b/src/updater.rs index f203786..a1a355b 100644 --- a/src/updater.rs +++ b/src/updater.rs @@ -154,6 +154,37 @@ pub async fn download_and_apply( Ok(current_exe) } +// ── Update-target file ─────────────────────────────────────────────────────── +// Written just before exec. On next startup we compare the expected version +// with CARGO_PKG_VERSION. A mismatch means the replacement didn't work +// (wrong asset, failed rename, etc.) and we show an error instead of looping. + +fn update_target_path() -> std::path::PathBuf { + crate::config::config_path() + .parent() + .expect("config dir has parent") + .join(".update_target") +} + +/// Write the expected version to disk before exec'ing the new binary. +pub fn write_update_target(new_version: &str) { + let _ = std::fs::write(update_target_path(), new_version); +} + +/// Read and delete the update-target file. +/// Returns `Some(expected_version)` if a previous run attempted an update. +/// The caller should compare it with `env!("CARGO_PKG_VERSION")` and show +/// an error if they differ. +pub fn take_update_target() -> Option { + let path = update_target_path(); + if !path.exists() { + return None; + } + let version = std::fs::read_to_string(&path).ok()?; + let _ = std::fs::remove_file(&path); + Some(version.trim().to_string()) +} + /// Replaces current process via execv (Unix). Never returns on success. /// `exe_path` must be the path captured *before* the binary was replaced — /// do NOT call std::env::current_exe() here, it returns "(deleted)" on Linux.