Check for updates at every start; show "Update to X" as a title-bar pill (#1538)
Thanks @bherbruck.
This commit is contained in:
1 parent
4d862cd76c
commit
6c1a898e3d
11 files changed
+268
-50
No files matched your search
@@ -1,5 +1,5 @@
|
||||
//! Update notifications (docs/updates.md). At start, and at most once a day after that, a
|
||||
//! background thread asks GitHub for the latest stable release (`releases/latest`; nightlies and
|
||||
//! Update notifications (docs/book/src/guide/updates.md). At every start, and once a day after
|
||||
//! that while the app stays open, a background thread asks GitHub for the latest stable release (`releases/latest`; nightlies and
|
||||
//! release candidates are never "latest"). When it is newer than this build, an Update button
|
||||
//! appears in the application bar beside Report a bug; it opens a small window with the version,
|
||||
//! the release notes and Download, Skip this version and Remind me later.
|
||||
@@ -32,17 +32,19 @@ pub const RELEASES_PAGE: &str = "https://github.com/bherbruck/solidcraft/release
|
||||
/// Release pages and assets must be on GitHub, under this repository.
|
||||
const RELEASE_PREFIX: &str = "https://github.com/bherbruck/solidcraft/releases/";
|
||||
|
||||
/// Automatic checks: at most one a day.
|
||||
/// While the app stays open, automatic checks run once a day (every start checks too).
|
||||
pub const CHECK_INTERVAL_S: f64 = 24.0 * 3600.0;
|
||||
/// Remind me later hides the button for three days.
|
||||
pub const REMIND_DELAY_S: f64 = 3.0 * 24.0 * 3600.0;
|
||||
/// Check now can't be pressed more often than this (GitHub allows 60 anonymous requests an hour).
|
||||
pub const MANUAL_COOLDOWN_S: f64 = 30.0;
|
||||
/// After a failed automatic check, try again after this long (an hour), not every start.
|
||||
/// After a failed automatic check, the open app tries again after this long (an hour).
|
||||
pub const RETRY_AFTER_FAILURE_S: f64 = 3600.0;
|
||||
/// Caps on what a release response may hold (it comes from the network).
|
||||
const MAX_NOTES: usize = 64 * 1024;
|
||||
const MAX_ASSETS: usize = 400;
|
||||
/// The room the Update pill's long label leaves for the document tabs.
|
||||
const TAB_ROOM: f32 = 300.0;
|
||||
/// The Update window's width, and its release notes' wrap width.
|
||||
const WINDOW_W: f32 = 460.0;
|
||||
const NOTES_W: f32 = WINDOW_W - 30.0;
|
||||
@@ -407,7 +409,8 @@ impl Release {
|
||||
pub struct Memo {
|
||||
/// When the last check finished (successfully or not).
|
||||
pub last_check: f64,
|
||||
/// The next automatic check: a day after a successful one, an hour after a failed one.
|
||||
/// The next automatic check while the app stays open: a day after a successful one, an hour
|
||||
/// after a failed one. A new start doesn't wait for it.
|
||||
pub next_check: f64,
|
||||
/// GitHub's rate limit was reached: no request at all before this, not even Check now.
|
||||
pub rate_limited_until: f64,
|
||||
@@ -418,9 +421,13 @@ pub struct Memo {
|
||||
}
|
||||
|
||||
impl Memo {
|
||||
/// An automatic check is due (a clock set back by more than a day counts as due).
|
||||
pub fn due(&self, now: f64) -> bool {
|
||||
now >= self.rate_limited_until && (now >= self.next_check || self.next_check - now > CHECK_INTERVAL_S)
|
||||
/// An automatic check is due. `launch`: no automatic check has run since SolidCraft started,
|
||||
/// so one is due whatever the daily schedule says (someone who started yesterday's version
|
||||
/// this morning hears about today's release at the next start). GitHub's rate limit always
|
||||
/// holds. A clock set back by more than a day counts as due.
|
||||
pub fn due(&self, now: f64, launch: bool) -> bool {
|
||||
let limited = now < self.rate_limited_until && self.rate_limited_until - now <= CHECK_INTERVAL_S;
|
||||
!limited && (launch || now >= self.next_check || self.next_check - now > CHECK_INTERVAL_S)
|
||||
}
|
||||
|
||||
/// The Update button shows for `release`.
|
||||
@@ -484,6 +491,10 @@ pub struct Updater {
|
||||
/// The check in flight, and whether Check now started it.
|
||||
rx: Option<(Receiver<Result<Release, FetchError>>, bool)>,
|
||||
started: bool,
|
||||
/// An automatic check has started since launch (until then one is due at once).
|
||||
launch_checked: bool,
|
||||
/// `SOLIDCRAFT_UPDATE_CHECK=0`: no checks at all.
|
||||
off: bool,
|
||||
/// When Check now was last pressed (Unix seconds).
|
||||
last_manual: f64,
|
||||
/// A release given by `SOLIDCRAFT_UPDATE_FEED` (a file), for tests and screenshots.
|
||||
@@ -511,10 +522,11 @@ pub fn offered(app: &SolveApp) -> Option<&Release> {
|
||||
|
||||
/// Checks may run here: a release build with the host's HTTP client, or a test feed.
|
||||
pub fn available(app: &mut SolveApp) -> bool {
|
||||
app.updater.feed.is_some() || (app.services.http_get.is_some() && app.updater.kind().checks())
|
||||
!app.updater.off && (app.updater.feed.is_some() || (app.services.http_get.is_some() && app.updater.kind().checks()))
|
||||
}
|
||||
|
||||
/// Per frame: the first frame starts a due automatic check; a finished check is collected.
|
||||
/// Per frame: the first frame of each launch starts an automatic check, the open app checks
|
||||
/// again once a day (an hour after a failure), and a finished check is collected.
|
||||
pub fn tick(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
poll_install(app);
|
||||
// Quitting to restart or install was cancelled (a "save changes?" answered Cancel).
|
||||
@@ -527,6 +539,7 @@ pub fn tick(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
{
|
||||
install::clean_up_after_update(std::env::current_exe().ok().as_deref());
|
||||
if std::env::var("SOLIDCRAFT_UPDATE_CHECK").is_ok_and(|v| v == "0") {
|
||||
app.updater.off = true;
|
||||
return;
|
||||
}
|
||||
app.updater.feed = std::env::var_os("SOLIDCRAFT_UPDATE_FEED").map(PathBuf::from);
|
||||
@@ -534,7 +547,9 @@ pub fn tick(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
}
|
||||
poll(app);
|
||||
let now = unix_now();
|
||||
if app.updater.rx.is_none() && app.preferences.check_updates && app.preferences.update.due(now) && available(app) {
|
||||
let launch = !app.updater.launch_checked;
|
||||
if app.updater.rx.is_none() && app.preferences.check_updates && app.preferences.update.due(now, launch) && available(app) {
|
||||
app.updater.launch_checked = true;
|
||||
start(app, ctx, false);
|
||||
}
|
||||
}
|
||||
@@ -714,11 +729,15 @@ pub fn take_launch(app: &mut SolveApp) -> Option<(PathBuf, Vec<String>)> {
|
||||
|
||||
// ---- user interface ---------------------------------------------------------------------------
|
||||
|
||||
/// The application bar's Update button, at the right of `span`; returns its rect (None: not shown).
|
||||
/// The application bar's Update pill ("Update to 0.14.0"), at the right of `span`: it opens and
|
||||
/// closes the Update window. Returns its rect (None: not shown).
|
||||
pub fn button(app: &mut SolveApp, ui: &mut egui::Ui, span: egui::Rect, labeled: bool) -> Option<egui::Rect> {
|
||||
offered(app)?;
|
||||
let (rect, response) = crate::toolbar::offer_button(ui, span, labeled, crate::i18n::tr("update.button"), "update", "titlebar:update")?;
|
||||
let version = app.updater.release.as_ref().map(|r| r.version().to_string()).unwrap_or_default();
|
||||
let version = offered(app)?.version().to_string();
|
||||
// "Update to 0.14.0" while the document tabs keep room; a short "Update" when they would be
|
||||
// squeezed; the icon alone in a narrow bar.
|
||||
let full = crate::i18n::format("update.button_version", &[("version", &version)]);
|
||||
let labels = [full.as_str(), crate::i18n::tr("update.button")];
|
||||
let (rect, response) = crate::toolbar::offer_pill(ui, span, labeled, &labels, TAB_ROOM, "update", "titlebar:update")?;
|
||||
if response.on_hover_text(crate::i18n::format("update.button_tip", &[("version", &version)])).clicked() {
|
||||
app.updater.open = !app.updater.open;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user