Files
craft-libs/crates/update/src/lib.rs
T
cdhdt a9e5afebf8 craft-update: the update dialogs take the app's translator
The startup prompt, the Check for Updates result card and the messages the Updater puts in the
prompt were English whatever the app's interface language (storytold/wordcraft#5257).
`StartupPrompt`, `CheckResultDialog` and `Updater` get `with_translator(Translate)`: a
`fn(&str, &[(&str, &str)]) -> String` that gets the English text (the key, as in the craft-i18n
catalogs) and its `{name}` values, so translations may reorder them and an app on craft-i18n
passes `craft_i18n::fmt(i18n.t(lang, text), args)`. It is called when the card draws, so a
language change shows on the next frame. Buttons are looked up in title case and drawn in
capitals, so the apps' existing button rows serve. Without a translator nothing changes.
2026-11-02 10:55:49 +01:00

110 lines
3.9 KiB
Rust

//! Release checking, downloading, installing and self-updating for the Craft App Suite and ArtCraft Launcher.
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
pub mod app;
pub mod desktop_entry;
pub mod detect;
pub mod distro;
mod folders;
pub mod http;
pub mod install;
pub mod manifest;
pub mod msi_context;
mod msi_copy;
mod msi_migrate;
pub mod packages;
pub mod paths;
pub mod pins;
pub mod platform;
#[cfg(windows)]
pub mod portable;
pub mod process;
pub mod protocol;
pub mod proxy;
pub mod queue;
pub mod release;
pub mod self_update;
pub mod source;
pub mod status;
pub mod suite;
pub mod time;
pub mod translate;
pub mod version;
#[cfg(feature = "egui")]
pub mod dialog;
#[cfg(feature = "egui")]
pub mod updater;
pub use app::App;
pub use http::{Http, Stop};
pub use paths::Paths;
pub use platform::{Arch, Os, PackageKind, Platform, pick_asset};
pub use protocol::{InstallLocations, JobPhase, LatestRelease, Source};
pub use release::{Asset, Release, parse_github_release, parse_github_release_list};
pub use status::{InstallSource, Installed};
pub use suite::SUITE;
pub use time::parse_rfc3339;
pub use version::{Version, is_newer, parse as parse_version};
#[cfg(feature = "egui")]
pub use dialog::{CheckResultDialog, CheckResultResponse, StartupPrompt, StartupPromptResponse};
/// Checks for an update against GitHub releases for the given app.
pub fn check_for_update(app: &App, http: &Http, current_version: &str) -> Result<Option<Release>, String> {
let version = self_update::current_version(app, current_version).map(|v| v.to_string());
let current_version = version.as_deref().unwrap_or(current_version);
let url = format!("https://api.github.com/repos/{}/releases?per_page=30", app.repo);
match http.get_api(&url, None)? {
crate::http::Fetched::Body { text, .. } => offered_release(&text, app.repo, current_version),
crate::http::Fetched::RateLimited { .. } => Err("GitHub's request limit was reached".into()),
crate::http::Fetched::NotModified | crate::http::Fetched::NotFound => Ok(None),
}
}
/// Unix seconds now.
pub fn now() -> i64 {
std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map_or(0, |d| i64::try_from(d.as_secs()).unwrap_or(i64::MAX))
}
/// The release in GitHub's release list `json` to offer a copy running `current_version`, if any:
/// the latest published release, when [`version::offers_update`] says so.
fn offered_release(json: &str, repo: &str, current_version: &str) -> Result<Option<Release>, String> {
let latest = crate::release::parse_github_release_list(json, repo, false)?;
Ok(latest.filter(|release| {
let running = crate::version::parse(current_version);
release.version.as_ref().zip(running.as_ref()).is_some_and(|(rel_v, cur_v)| crate::version::offers_update(rel_v, cur_v))
}))
}
#[cfg(test)]
pub(crate) const TEST_AGENT: &str = "ArtCraft-Launcher-Test/0.0";
#[cfg(test)]
mod offer_tests {
use super::offered_release;
const REPO: &str = "storytold/photocraft";
const LIST: &str = r#"[{"tag_name":"v0.17.0","prerelease":false,"draft":false,"assets":[]},
{"tag_name":"v0.16.4","prerelease":false,"draft":false,"assets":[]}]"#;
fn offered(running: &str) -> Option<String> {
offered_release(LIST, REPO, running).unwrap().map(|release| release.tag)
}
#[test]
fn a_build_from_main_is_not_offered_the_release_it_already_contains() {
assert_eq!(offered("0.17.0-integration.20261029.f5b3f123"), None);
assert_eq!(offered("0.16.5-dev"), Some("v0.17.0".into()));
}
#[test]
fn releases_and_release_candidates_are_offered_as_before() {
assert_eq!(offered("0.17.0-rc.2"), Some("v0.17.0".into()));
assert_eq!(offered("0.16.4"), Some("v0.17.0".into()));
assert_eq!(offered("0.17.0"), None);
assert_eq!(offered("not a version"), None);
}
}