use clap::Subcommand;
use flatland_client_lib::{
apply_update, check_for_update, default_latest_url, local_version, UpdateAvailable,
};
#[derive(Debug, Subcommand)]
pub enum UpdateCommand {
Check {
#[arg(long, env = "FLATLAND_UPDATE_URL")]
update_url: Option<String>,
},
Apply {
#[arg(long, env = "FLATLAND_UPDATE_URL")]
update_url: Option<String>,
#[arg(long)]
force: bool,
},
}
pub async fn run(command: UpdateCommand) -> anyhow::Result<()> {
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(60))
.build()?;
match command {
UpdateCommand::Check { update_url } => {
let local = local_version();
match check_for_update(&client, local, update_url.as_deref()).await? {
Some(offer) => {
println!(
"update available: {} → {} ({})",
offer.local_version, offer.remote_version, offer.platform
);
println!("archive: {}", offer.archive_url);
println!("run: flatland3 update apply");
}
None => {
println!(
"up to date: v{local} (checked {})",
update_url.unwrap_or_else(default_latest_url)
);
}
}
Ok(())
}
UpdateCommand::Apply { update_url, force } => {
let local = local_version();
let offer = if force {
force_offer(&client, local, update_url.as_deref()).await?
} else {
check_for_update(&client, local, update_url.as_deref())
.await?
.ok_or_else(|| anyhow::anyhow!("already up to date (v{local})"))?
};
println!(
"downloading {} ({} bytes)…",
offer.remote_version, offer.artifact.size
);
let applied = apply_update(&client, &offer).await?;
println!(
"updated to v{} in {}",
applied.version,
applied.install_dir.display()
);
println!("restart flatland3 / flatland3-gfx to run the new binaries");
Ok(())
}
}
}
async fn force_offer(
client: &reqwest::Client,
local: &str,
latest_url: Option<&str>,
) -> anyhow::Result<UpdateAvailable> {
use flatland_client_lib::{fetch_latest_manifest, platform_tag};
let url = latest_url
.map(str::to_string)
.unwrap_or_else(default_latest_url);
let platform = platform_tag().ok_or_else(|| {
anyhow::anyhow!(
"unsupported platform ({}-{})",
std::env::consts::OS,
std::env::consts::ARCH
)
})?;
let manifest = fetch_latest_manifest(client, &url).await?;
let artifact = manifest
.artifacts
.get(platform)
.cloned()
.ok_or_else(|| anyhow::anyhow!("latest.json missing artifact for {platform}"))?;
let archive_url = if let Some((base, _)) = url.rsplit_once('/') {
format!("{}/{}", base, artifact.path.trim_start_matches('/'))
} else {
artifact.path.clone()
};
Ok(UpdateAvailable {
local_version: local.to_string(),
remote_version: manifest.version,
platform: platform.to_string(),
artifact,
archive_url,
})
}