#![allow(dead_code)]
use crate::update::checker::{CURRENT_VERSION, CheckResult, UpdateChecker};
use crate::update::config::{Channel, UpdateConfig, is_interactive};
use crate::update::installer::BinaryInstaller;
use crate::update::method::{InstallMethod, UpdateError};
use crate::update::release::ReleaseClient;
use crate::update::rollback::RollbackManager;
#[derive(Debug, Clone)]
pub enum UpdateAction {
Check {
channel: Option<Channel>,
},
Install {
version: Option<String>,
channel: Option<Channel>,
method: Option<InstallMethod>,
rollback: bool,
allow_unsigned: bool,
},
History,
Config,
Enable,
Disable,
}
pub fn run_update_command(action: UpdateAction) -> i32 {
let result = match action {
UpdateAction::Check { channel } => run_check(channel),
UpdateAction::Install {
version,
channel,
method,
rollback,
allow_unsigned,
} => {
if rollback {
run_rollback()
} else {
run_install(version, channel, method, allow_unsigned)
}
}
UpdateAction::History => run_history(),
UpdateAction::Config => run_config(),
UpdateAction::Enable => run_enable(),
UpdateAction::Disable => run_disable(),
};
match result {
Ok(()) => 0,
Err(e) => {
eprintln!("Update error: {}", e);
1
}
}
}
fn run_check(channel_override: Option<Channel>) -> Result<(), UpdateError> {
let mut checker = UpdateChecker::new();
if let Some(ch) = channel_override {
let mut config = checker.config().clone();
config.channel = ch;
checker = UpdateChecker::with_config(config);
}
println!("Checking for updates...");
println!("Current version: {}", CURRENT_VERSION);
println!("Channel: {}", checker.config().channel);
match checker.check() {
Ok(CheckResult::UpToDate) => {
println!("\nJarvy is up to date!");
Ok(())
}
Ok(CheckResult::UpdateAvailable {
current,
latest,
changelog,
release_url,
}) => {
println!("\nUpdate available!");
println!(" Current: {}", current);
println!(" Latest: {}", latest);
if let Some(url) = release_url {
println!("\n Release notes: {}", url);
}
if let Some(log) = changelog {
println!("\nWhat's new:");
for line in log.lines().take(10) {
println!(" {}", line);
}
}
println!("\nRun 'jarvy update' to install the update.");
Ok(())
}
Err(e) => {
eprintln!("Failed to check for updates: {}", e);
Err(e.into())
}
}
}
fn run_install(
version: Option<String>,
channel: Option<Channel>,
method: Option<InstallMethod>,
allow_unsigned: bool,
) -> Result<(), UpdateError> {
let _channel = channel.unwrap_or(UpdateConfig::load().channel);
let method = method.unwrap_or_else(InstallMethod::detect);
println!("Installation method: {}", method);
let target_version = if let Some(v) = version {
v
} else {
let mut checker = UpdateChecker::new();
match checker.check() {
Ok(CheckResult::UpToDate) => {
println!("Jarvy v{} is already up to date!", CURRENT_VERSION);
return Ok(());
}
Ok(CheckResult::UpdateAvailable { latest, .. }) => {
if !is_interactive() {
println!("Update available: {} -> {}", CURRENT_VERSION, latest);
} else {
println!("Update available: {} -> {}", CURRENT_VERSION, latest);
println!("Proceed with installation? [Y/n]");
let mut input = String::new();
std::io::stdin().read_line(&mut input).ok();
if !input.trim().is_empty() && input.trim().to_lowercase() != "y" {
println!("Update cancelled.");
return Ok(());
}
}
latest
}
Err(e) => {
return Err(e.into());
}
}
};
println!("Updating to version {}...", target_version);
if method.supports_direct_update() {
method.execute_update(Some(&target_version))?;
} else {
let client = ReleaseClient::new();
let release = client
.fetch_by_tag(&format!("v{}", target_version))
.map_err(|e| UpdateError::DownloadFailed(e.to_string()))?;
let installer =
BinaryInstaller::new().map_err(|e| UpdateError::InstallationFailed(e.to_string()))?;
installer.install_with_options(&release, allow_unsigned)?;
}
println!("\nSuccessfully updated to jarvy v{}", target_version);
println!(" Run 'jarvy --version' to verify.");
Ok(())
}
fn run_rollback() -> Result<(), UpdateError> {
let Some(info) = RollbackManager::info() else {
println!("No rollback available.");
println!("Rollback is only available immediately after an update.");
return Ok(());
};
if !RollbackManager::can_rollback() {
println!("No rollback available.");
println!("Rollback is only available immediately after an update.");
return Ok(());
}
println!(
"Rolling back from {} to {}...",
info.new_version, info.previous_version
);
let result = RollbackManager::rollback()?;
println!("\nRolled back to jarvy v{}", result.restored_version);
println!(" The backup has been consumed and cannot be used again.");
Ok(())
}
fn run_history() -> Result<(), UpdateError> {
if let Some(info) = RollbackManager::info() {
println!("Last update:");
println!(" From: v{}", info.previous_version);
println!(" To: v{}", info.new_version);
println!(" Backup: {}", info.backup_path.display());
println!(" Rollback available: yes");
} else {
println!("No update history available.");
println!("History is recorded when updates are installed via 'jarvy update'.");
}
Ok(())
}
fn run_config() -> Result<(), UpdateError> {
let config = UpdateConfig::load();
let method = InstallMethod::detect();
let checker = UpdateChecker::new();
println!("Update Configuration:");
println!(" Enabled: {}", config.enabled);
println!(" Channel: {}", config.channel);
println!(" Auto-install: {:?}", config.auto_install);
println!(" Check interval: {:?}", config.check_interval);
println!(" Patch only: {}", config.patch_only);
println!(" Notifications: {}", config.show_notifications);
if let Some(ref pin) = config.pinned_version {
println!(" Pinned version: {}", pin);
}
println!("\nDetected install method: {}", method);
println!("Current version: {}", CURRENT_VERSION);
if let Some(last) = checker.state().last_checked {
let datetime = format_timestamp(last);
println!("Last checked: {}", datetime);
}
if let Some(ref avail) = checker.state().available_version {
println!("Available version: {}", avail);
}
if RollbackManager::can_rollback() {
println!("\nRollback available: yes");
}
Ok(())
}
fn run_enable() -> Result<(), UpdateError> {
let mut config = UpdateConfig::load();
config.enabled = true;
config.save().map_err(UpdateError::Io)?;
println!("Automatic updates enabled.");
println!(" Jarvy will check for updates periodically.");
Ok(())
}
fn run_disable() -> Result<(), UpdateError> {
let mut config = UpdateConfig::load();
config.enabled = false;
config.save().map_err(UpdateError::Io)?;
println!("Automatic updates disabled.");
println!(" You can still manually check with 'jarvy update check'.");
Ok(())
}
fn format_timestamp(timestamp: u64) -> String {
use std::time::{Duration, UNIX_EPOCH};
let time = UNIX_EPOCH + Duration::from_secs(timestamp);
match time.elapsed() {
Ok(elapsed) => {
let secs = elapsed.as_secs();
if secs < 60 {
"just now".to_string()
} else if secs < 3600 {
format!("{} minutes ago", secs / 60)
} else if secs < 86400 {
format!("{} hours ago", secs / 3600)
} else {
format!("{} days ago", secs / 86400)
}
}
Err(_) => "unknown".to_string(),
}
}
impl From<crate::update::checker::CheckError> for UpdateError {
fn from(e: crate::update::checker::CheckError) -> Self {
UpdateError::DownloadFailed(e.to_string())
}
}
pub fn show_update_notification_if_available() {
let mut checker = UpdateChecker::new();
if checker.should_notify() {
if let Some(msg) = checker.notification_message() {
eprintln!("\n{}", msg);
checker.mark_notified();
}
}
}