Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Delete locally stored update and download more recent version #2469

Open
kaylagalway opened this issue Nov 14, 2023 · 5 comments
Open

Delete locally stored update and download more recent version #2469

kaylagalway opened this issue Nov 14, 2023 · 5 comments

Comments

@kaylagalway
Copy link

Summary

If Sparkle has already downloaded an update, but my macOS application has not yet installed/completed the update, I would like to be able to check for newer updates and override the older downloaded update.

I can not find any function/stored property that would handle this functionality as of right now. It seems like I would need to manually delete the stored downloaded update and manually reset things, which feels unsafe.

Am I missing the straight forward way to handle this?

Possible Fix

Adjust the logic in checkForUpdates so that we can check the sparkle feed for a new update, and if an old one is already downloaded, we discard it and prioritize the newer update.

Version

All

@zorgiepoo
Copy link
Member

zorgiepoo commented Nov 15, 2023

This was last discussed in #2092. It isn't possible. I don't think I'd be happy with how this would work with deltas in Sparkle's architecture (and I'm unsure about reentrancy for the helpers too). Today you can only nudge the user about the currently downloaded update or try to force updating the app. Sparkle by default nudges the user after a week has passed.

Also if the user manually checks for updates (i.e checkForUpdates), it's much faster to install if Sparkle has already downloaded/staged that update for installation, than it is to download/extract/install a newer one.

@kaylagalway
Copy link
Author

kaylagalway commented Nov 15, 2023

My main use case is this:

If we have released a broken build that crashes on launch and a user has already downloaded that build, but not restarted to install it - they should be able to manually checkForUpdates and install a newer safe build that we released to remedy the issue. Or we should be able to force an update check to trigger without them knowing and download the new safe build. Otherwise a user will have no choice but to update to the known broken build or uninstall/reinstall.

Is there any way this could be reconsidered?

@zorgiepoo
Copy link
Member

zorgiepoo commented Nov 18, 2023

Personally, the most important issue in that scenario is shipping a build that broken and most of the effort should go into improving qualification process to minimize that from happening in the future. Even with this change, you can still end up with a lot of users installing your broken build. Not all users can silently install updates or users that can could quit the app / reboot the system before such a hot-fix is delivered. New users could download your app online.

If we want to make this change, the rationale needs to be that it's better to opportunistically install a more newer update, not assume that an old update may be bad.

The way I think this potential change might work:

  • After an update is downloaded, the updater checks for updates on regular scheduled basis instead of using the impatient update check interval
  • The impatient update check interval will need to be tracked separately still for showing the update to the user after a long time passed and probably should not be user-default back based but instead local-timer based
  • The updater re-runs the automatic update driver even when the installer is already running
    • Not if the update is critical marked or info only or major upgrade which shows up to user promptly instead (i.e no resumable update I believe)
    • Not if an external updater is being used and the bundle to update is not the main bundle (to prevent multiple updaters fighting against each other)
    • Not if the update would require user authorization again and the update has already been staged for installation (asking the user to authorize to install an update again is bad. This case doesn't presently affect Sparkle's standard UI which holds onto an update after user authorizes but it may affect custom user drivers that end their cycle after authorizing to start the installer.).
  • The automatic update driver checks the update feed and selects the best appcast item to choose. If it's different than the currently downloaded update, it proceeds downloading that update. If they're the same, the automatic update driver aborts and ends the update cycle; if an update was already downloaded but not staged to be installed (due to requiring authorization but user deferring the install), the automatic update driver needs to preserve the resumable update and propagate it onto next cycle.
  • When the install driver tries to start a new installation, it needs to first properly send a message to cancel the update from an existing connection/session which will terminate the helpers. Then it can start a new job when that connection has been invalidated (need to prevent potential races for both installer and status service/helpers) and spawns the helpers again. The install helpers are not designed to be re-entrant and trust another downloaded update 'coming in' when an update has already been staged for installation. This will create a period of time where no update will be staged for installation (while the new update needs to be extracted, validated, etc) which I don't like but may be acceptable. Also it may rarely be possible for canceling the update to fail if another external updater is holding a connection open to it, then this new download would have to be canceled. Open to other ideas here, maybe.
  • The UI scheduled update driver (both automatically scheduled and manual when users manually checks for updates) should not be changed. Hitting the server when the update driver finds no new update different than the one already downloaded makes a common case perceivably slow which I don't find acceptable and we can avoid further complexity by not updating those drivers.

I did not want to work on this and didn't think the extra complexity / re-architecture / bug-risk cost outweighed the benefit. This is why I closed #2092. It is also not presently a highly requested feature (it's a rarely requested one even); many developers do not ship updates that frequently in a short interval. I can leave this issue open for a bit if anyone wants to explore this further and is interested though.

If it helps any, Sparkle also provides phased group rollouts. Users today can also manually skip an update that has been downloaded if they happen to know it's bad (e.g, if it's indicated in the release notes or something and they read it); that will delete the downloaded update.

@kaylagalway
Copy link
Author

kaylagalway commented Dec 15, 2023

Thank you for such a thorough response!

You are right that the bigger opportunity here is definitely the case where a user cannot easily download the most recent update if they already have one installed. This leaves users in a situation where they may not realize it, but they already have an update downloaded, so they are not updating to the most recent version when they checkForUpdates and they still have to restart twice to update to the most recent update.

From your description, it seems the technical lift on this improvement is quite significant. One question I had after reading is: if a user can currently opt to manually skip an update, could that same machinery be used when they click the button to checkForUpdates?

i.e. could checkForUpdates use the same logic that skipping an update does to delete the downloaded update and download a new one? When a user skips an update, they can still check for new updates afterwards right?

@zorgiepoo
Copy link
Member

Note the more common case is not the user checking for updates, but the update being installed in the background by the automatic update driver.

A problem is you need to know when to skip an update. You only want to skip an update if a newer update is available. Sparkle doesn't presently hit the server again when an update is already downloaded, even if the user checks for updates within the app.

As far as I can this doesn't change my first 4 points, may change how point 5 is implemented (i.e, when to cancel the existing update), and we may disagree about point 6.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants