use anyhow::{Context as _, Result};use client::Client;use db::kvp::KeyValueStore;use futures_lite::StreamExt;use gpui::{ App, AppContext as _, AsyncApp, BackgroundExecutor, Context, Entity, Global, Task, TaskExt, Window, actions,};use http_client::{HttpClient, HttpClientWithUrl};use paths::remote_servers_dir;use release_channel::{AppCommitSha, ReleaseChannel};use semver::Version;use serde::{Deserialize, Serialize};use settings::{RegisterSetting, Settings, SettingsStore};use smol::fs::File;use smol::{ fs, io::{AsyncReadExt, AsyncWriteExt},};use std::mem;use std::{ env::{ self, consts::{ARCH, OS}, }, ffi::OsStr, ffi::OsString, path::{Path, PathBuf}, sync::Arc, time::{Duration, SystemTime},};use util::command::new_command;use workspace::Workspace; const SHOULD_SHOW_UPDATE_NOTIFICATION_KEY: &str = "auto-updater-should-show-updated-notification"; #[derive(Debug)]struct MissingDependencyError(String); impl std::fmt::Display for MissingDependencyError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) }} impl std::error::Error for MissingDependencyError {}const POLL_INTERVAL: Duration = Duration::from_secs(60 * 60);const NIGHTLY_POLL_INTERVAL: Duration = Duration::from_secs(15 * 60);const REMOTE_SERVER_CACHE_LIMIT: usize = 5; #[cfg(target_os = "linux")]fn linux_rsync_install_hint() -> &'static str { let os_release = match std::fs::read_to_string("/etc/os-release") { Ok(os_release) => os_release, Err(_) => return "Please install rsync using your package manager", }; let mut distribution_ids = Vec::new(); for line in os_release.lines() { let trimmed = line.trim(); if let Some(value) = trimmed.strip_prefix("ID=") { distribution_ids.push(value.trim_matches('"').to_ascii_lowercase()); } else if let Some(value) = trimmed.strip_prefix("ID_LIKE=") { for id in value.trim_matches('"').split_whitespace() { distribution_ids.push(id.to_ascii_lowercase()); } } } let package_manager_hint = if distribution_ids .iter() .any(|distribution_id| distribution_id == "arch") { Some("Install it with: sudo pacman -S rsync") } else if distribution_ids .iter() .any(|distribution_id| distribution_id == "debian" || distribution_id == "ubuntu") { Some("Install it with: sudo apt install rsync") } else if distribution_ids.iter().any(|distribution_id| { distribution_id == "fedora" || distribution_id == "rhel" || distribution_id == "centos" || distribution_id == "rocky" || distribution_id == "almalinux" }) { Some("Install it with: sudo dnf install rsync") } else if distribution_ids .iter() .any(|distribution_id| distribution_id == "nixos") { Some("Install pkgs.rsync from nixpkgs") } else { None }; package_manager_hint.unwrap_or("Please install rsync using your package manager")} actions!( auto_update, [ /// Checks for available updates. Check, /// Dismisses the update error message. DismissMessage, /// Opens the release notes for the current version in a browser. ViewReleaseNotes, ]); #[derive(Serialize, Debug)]pub struct AssetQuery<'a> { asset: &'a str, os: &'a str, arch: &'a str, metrics_id: Option<&'a str>, system_id: Option<&'a str>, is_staff: Option<bool>,} #[derive(Clone, Debug)]pub enum AutoUpdateStatus { Idle, Checking, Downloading { version: Version, /// Download progress as a fraction in the range `0.0..=1.0`, or `None` /// when the total download size is not yet known. progress: Option<f32>, }, Installing { version: Version, }, Updated { version: Version, }, Errored { error: Arc<anyhow::Error>, },} impl PartialEq for AutoUpdateStatus { // `progress` is deliberately not compared: two `Downloading` statuses for // the same version are equal regardless of how far the download is. fn eq(&self, other: &Self) -> bool { match (self, other) { (AutoUpdateStatus::Idle, AutoUpdateStatus::Idle) => true, (AutoUpdateStatus::Checking, AutoUpdateStatus::Checking) => true, ( AutoUpdateStatus::Downloading { version: v1, .. }, AutoUpdateStatus::Downloading { version: v2, .. }, ) => v1 == v2, ( AutoUpdateStatus::Installing { version: v1 }, AutoUpdateStatus::Installing { version: v2 }, ) => v1 == v2, ( AutoUpdateStatus::Updated { version: v1 }, AutoUpdateStatus::Updated { version: v2 }, ) => v1 == v2, (AutoUpdateStatus::Errored { error: e1 }, AutoUpdateStatus::Errored { error: e2 }) => { e1.to_string() == e2.to_string() } _ => false, } }} impl AutoUpdateStatus { pub fn is_updated(&self) -> bool { matches!(self, Self::Updated { .. }) }} pub struct AutoUpdater { status: AutoUpdateStatus, current_version: Version, client: Arc<Client>, pending_poll: Option<Task<Option<()>>>, quit_subscription: Option<gpui::Subscription>, update_check_type: UpdateCheckType, dismissed_status: Option<AutoUpdateStatus>,} #[derive(Deserialize, Serialize, Clone, Debug)]pub struct ReleaseAsset { pub version: String, pub url: String,} struct MacOsUnmounter<'a> { mount_path: PathBuf, background_executor: &'a BackgroundExecutor,} impl MacOsUnmounter<'_> { /// Unmounts the disk image and waits for completion. This must happen /// before the `InstallerDir` is dropped: deleting the temp dir while the /// image is still mounted inside it fails silently and leaks the /// directory (and the downloaded DMG) in the system temp dir. async fn unmount(mut self) { let mount_path = mem::take(&mut self.mount_path); unmount_disk_image(&mount_path).await; }} impl Drop for MacOsUnmounter<'_> { fn drop(&mut self) { let mount_path = mem::take(&mut self.mount_path); // Safety net for early exits and cancellation; the happy path calls // `unmount`, which leaves the path empty. if mount_path.as_os_str().is_empty() { return; } self.background_executor .spawn(async move { unmount_disk_image(&mount_path).await }) .detach(); }} async fn unmount_disk_image(mount_path: &Path) { let unmount_output = new_command("hdiutil") .args(["detach", "-force"]) .arg(mount_path) .output() .await; match unmount_output { Ok(output) if output.status.success() => { log::info!("Successfully unmounted the disk image"); } Ok(output) => { log::error!( "Failed to unmount disk image: {:?}", String::from_utf8_lossy(&output.stderr) ); } Err(error) => { log::error!("Error while trying to unmount disk image: {:?}", error); } }} #[derive(Clone, Copy, Debug, RegisterSetting)]struct AutoUpdateSetting(bool); /// Whether or not to automatically check for updates.////// Default: trueimpl Settings for AutoUpdateSetting { fn from_settings(content: &settings::SettingsContent) -> Self { Self(content.auto_update.unwrap()) }} #[derive(Default)]struct GlobalAutoUpdate(Option<Entity<AutoUpdater>>); impl Global for GlobalAutoUpdate {} pub fn init(client: Arc<Client>, cx: &mut App) { cx.observe_new(|workspace: &mut Workspace, _window, _cx| { workspace.register_action(|_, action, window, cx| check(action, window, cx)); workspace.register_action(|_, action, _, cx| { view_release_notes(action, cx); }); }) .detach(); let version = release_channel::AppVersion::global(cx); let auto_updater = cx.new(|cx| { let updater = AutoUpdater::new(version, client, cx); let poll_for_updates = ReleaseChannel::try_global(cx) .map(|channel| channel.poll_for_updates()) .unwrap_or(false); if option_env!("ZED_UPDATE_EXPLANATION").is_none() && env::var("ZED_UPDATE_EXPLANATION").is_err() && poll_for_updates { let mut update_subscription = AutoUpdateSetting::get_global(cx) .0 .then(|| updater.start_polling(cx)); cx.observe_global::<SettingsStore>(move |updater: &mut AutoUpdater, cx| { if AutoUpdateSetting::get_global(cx).0 { if update_subscription.is_none() { update_subscription = Some(updater.start_polling(cx)) } } else { update_subscription.take(); } }) .detach(); } updater }); cx.set_global(GlobalAutoUpdate(Some(auto_updater)));} pub fn check(_: &Check, window: &mut Window, cx: &mut App) { if let Some(message) = option_env!("ZED_UPDATE_EXPLANATION") .map(ToOwned::to_owned) .or_else(|| env::var("ZED_UPDATE_EXPLANATION").ok()) { drop(window.prompt( gpui::PromptLevel::Info, "Zed was installed via a package manager.", Some(&message), &["OK"], cx, )); return; } if !ReleaseChannel::try_global(cx) .map(|channel| channel.poll_for_updates()) .unwrap_or(false) { return; } if let Some(updater) = AutoUpdater::get(cx) { updater.update(cx, |updater, cx| updater.poll(UpdateCheckType::Manual, cx)); } else { drop(window.prompt( gpui::PromptLevel::Info, "Could not check for updates", Some("Auto-updates disabled for non-bundled app."), &["OK"], cx, )); }} pub fn release_notes_url(cx: &mut App) -> Option<String> { let release_channel = ReleaseChannel::try_global(cx)?; let url = match release_channel { ReleaseChannel::Stable | ReleaseChannel::Preview => { let auto_updater = AutoUpdater::get(cx)?; let auto_updater = auto_updater.read(cx); let mut current_version = auto_updater.current_version.clone(); current_version.pre = semver::Prerelease::EMPTY; current_version.build = semver::BuildMetadata::EMPTY; let release_channel = release_channel.dev_name(); let path = format!("/releases/{release_channel}/{current_version}"); auto_updater.client.http_client().build_url(&path) } ReleaseChannel::Nightly => { "https://github.com/zed-industries/zed/commits/nightly/".to_string() } ReleaseChannel::Dev => "https://github.com/zed-industries/zed/commits/main/".to_string(), }; Some(url)} pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut App) -> Option<()> { let url = release_notes_url(cx)?; cx.open_url(&url); None} #[cfg(not(target_os = "windows"))]const INSTALLER_DIR_PREFIX: &str = "zed-auto-update"; #[cfg(not(target_os = "windows"))]struct InstallerDir(tempfile::TempDir); #[cfg(not(target_os = "windows"))]impl InstallerDir { async fn new() -> Result<Self> { Ok(Self( tempfile::Builder::new() .prefix(INSTALLER_DIR_PREFIX) .tempdir()?, )) } fn path(&self) -> &Path { self.0.path() }} #[cfg(target_os = "windows")]struct InstallerDir(PathBuf); #[cfg(target_os = "windows")]impl InstallerDir { async fn new() -> Result<Self> { let installer_dir = std::env::current_exe()? .parent() .context("No parent dir for Zed.exe")? .join("updates"); if smol::fs::metadata(&installer_dir).await.is_ok() { smol::fs::remove_dir_all(&installer_dir).await?; } smol::fs::create_dir(&installer_dir).await?; Ok(Self(installer_dir)) } fn path(&self) -> &Path { self.0.as_path() }} #[derive(Clone, Copy, Debug, PartialEq)]pub enum UpdateCheckType { Automatic, Manual,} impl UpdateCheckType { pub fn is_manual(self) -> bool { self == Self::Manual }} impl AutoUpdater { pub fn get(cx: &mut App) -> Option<Entity<Self>> { cx.default_global::<GlobalAutoUpdate>().0.clone() } fn new(current_version: Version, client: Arc<Client>, cx: &mut Context<Self>) -> Self { // On windows, executable files cannot be overwritten while they are // running, so we must wait to overwrite the application until quitting // or restarting. When quitting the app, we spawn the auto update helper // to finish the auto update process after Zed exits. When restarting // the app after an update, we use `set_restart_path` to run the auto // update helper instead of the app, so that it can overwrite the app // and then spawn the new binary. #[cfg(target_os = "windows")] let quit_subscription = Some(cx.on_app_quit(|_, _| finalize_auto_update_on_quit())); #[cfg(not(target_os = "windows"))] let quit_subscription = None; cx.on_app_restart(|this, _| { this.quit_subscription.take(); }) .detach(); Self { status: AutoUpdateStatus::Idle, current_version, client, pending_poll: None, quit_subscription, update_check_type: UpdateCheckType::Automatic, dismissed_status: None, } } pub fn start_polling(&self, cx: &mut Context<Self>) -> Task<Result<()>> { let poll_interval = ReleaseChannel::try_global(cx).map_or(POLL_INTERVAL, |channel| match channel { ReleaseChannel::Nightly => NIGHTLY_POLL_INTERVAL, _ => POLL_INTERVAL, }); cx.spawn(async move |this, cx| { if cfg!(target_os = "windows") { use util::ResultExt; cleanup_windows() .await .context("failed to cleanup old directories") .log_err(); } #[cfg(all(not(target_os = "windows"), not(test)))] cx.background_spawn(cleanup_stale_installer_dirs()).detach(); loop { this.update(cx, |this, cx| this.poll(UpdateCheckType::Automatic, cx))?; cx.background_executor().timer(poll_interval).await; } }) } pub fn update_check_type(&self) -> UpdateCheckType { self.update_check_type } pub fn poll(&mut self, check_type: UpdateCheckType, cx: &mut Context<Self>) { if check_type.is_manual() { self.dismissed_status = None; } if self.pending_poll.is_some() { if self.update_check_type == UpdateCheckType::Automatic { self.update_check_type = check_type; cx.notify(); } return; } self.update_check_type = check_type; cx.notify(); Lines 1–500 of 1846. Load more
use anyhow::{Context as _, Result}; use client::Client; use db::kvp::KeyValueStore; use futures_lite::StreamExt; use gpui::{ App, AppContext as _, AsyncApp, BackgroundExecutor, Context, Entity, Global, Task, TaskExt, Window, actions, }; use http_client::{HttpClient, HttpClientWithUrl}; use paths::remote_servers_dir; use release_channel::{AppCommitSha, ReleaseChannel}; use semver::Version; use serde::{Deserialize, Serialize}; use settings::{RegisterSetting, Settings, SettingsStore}; use smol::fs::File; use smol::{ fs, io::{AsyncReadExt, AsyncWriteExt}, }; use std::mem; use std::{ env::{ self, consts::{ARCH, OS}, }, ffi::OsStr, ffi::OsString, path::{Path, PathBuf}, sync::Arc, time::{Duration, SystemTime}, }; use util::command::new_command; use workspace::Workspace; const SHOULD_SHOW_UPDATE_NOTIFICATION_KEY: &str = "auto-updater-should-show-updated-notification"; #[derive(Debug)] struct MissingDependencyError(String); impl std::fmt::Display for MissingDependencyError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) } } impl std::error::Error for MissingDependencyError {} const POLL_INTERVAL: Duration = Duration::from_secs(60 * 60); const NIGHTLY_POLL_INTERVAL: Duration = Duration::from_secs(15 * 60); const REMOTE_SERVER_CACHE_LIMIT: usize = 5; #[cfg(target_os = "linux")] fn linux_rsync_install_hint() -> &'static str { let os_release = match std::fs::read_to_string("/etc/os-release") { Ok(os_release) => os_release, Err(_) => return "Please install rsync using your package manager", }; let mut distribution_ids = Vec::new(); for line in os_release.lines() { let trimmed = line.trim(); if let Some(value) = trimmed.strip_prefix("ID=") { distribution_ids.push(value.trim_matches('"').to_ascii_lowercase()); } else if let Some(value) = trimmed.strip_prefix("ID_LIKE=") { for id in value.trim_matches('"').split_whitespace() { distribution_ids.push(id.to_ascii_lowercase()); } } } let package_manager_hint = if distribution_ids .iter() .any(|distribution_id| distribution_id == "arch") { Some("Install it with: sudo pacman -S rsync") } else if distribution_ids .iter() .any(|distribution_id| distribution_id == "debian" || distribution_id == "ubuntu") { Some("Install it with: sudo apt install rsync") } else if distribution_ids.iter().any(|distribution_id| { distribution_id == "fedora" || distribution_id == "rhel" || distribution_id == "centos" || distribution_id == "rocky" || distribution_id == "almalinux" }) { Some("Install it with: sudo dnf install rsync") } else if distribution_ids .iter() .any(|distribution_id| distribution_id == "nixos") { Some("Install pkgs.rsync from nixpkgs") } else { None }; package_manager_hint.unwrap_or("Please install rsync using your package manager") } actions!( auto_update, [ /// Checks for available updates. Check, /// Dismisses the update error message. DismissMessage, /// Opens the release notes for the current version in a browser. ViewReleaseNotes, ] ); #[derive(Serialize, Debug)] pub struct AssetQuery<'a> { asset: &'a str, os: &'a str, arch: &'a str, metrics_id: Option<&'a str>, system_id: Option<&'a str>, is_staff: Option<bool>, } #[derive(Clone, Debug)] pub enum AutoUpdateStatus { Idle, Checking, Downloading { version: Version, /// Download progress as a fraction in the range `0.0..=1.0`, or `None` /// when the total download size is not yet known. progress: Option<f32>, }, Installing { version: Version, }, Updated { version: Version, }, Errored { error: Arc<anyhow::Error>, }, } impl PartialEq for AutoUpdateStatus { // `progress` is deliberately not compared: two `Downloading` statuses for // the same version are equal regardless of how far the download is. fn eq(&self, other: &Self) -> bool { match (self, other) { (AutoUpdateStatus::Idle, AutoUpdateStatus::Idle) => true, (AutoUpdateStatus::Checking, AutoUpdateStatus::Checking) => true, ( AutoUpdateStatus::Downloading { version: v1, .. }, AutoUpdateStatus::Downloading { version: v2, .. }, ) => v1 == v2, ( AutoUpdateStatus::Installing { version: v1 }, AutoUpdateStatus::Installing { version: v2 }, ) => v1 == v2, ( AutoUpdateStatus::Updated { version: v1 }, AutoUpdateStatus::Updated { version: v2 }, ) => v1 == v2, (AutoUpdateStatus::Errored { error: e1 }, AutoUpdateStatus::Errored { error: e2 }) => { e1.to_string() == e2.to_string() } _ => false, } } } impl AutoUpdateStatus { pub fn is_updated(&self) -> bool { matches!(self, Self::Updated { .. }) } } pub struct AutoUpdater { status: AutoUpdateStatus, current_version: Version, client: Arc<Client>, pending_poll: Option<Task<Option<()>>>, quit_subscription: Option<gpui::Subscription>, update_check_type: UpdateCheckType, dismissed_status: Option<AutoUpdateStatus>, } #[derive(Deserialize, Serialize, Clone, Debug)] pub struct ReleaseAsset { pub version: String, pub url: String, } struct MacOsUnmounter<'a> { mount_path: PathBuf, background_executor: &'a BackgroundExecutor, } impl MacOsUnmounter<'_> { /// Unmounts the disk image and waits for completion. This must happen /// before the `InstallerDir` is dropped: deleting the temp dir while the /// image is still mounted inside it fails silently and leaks the /// directory (and the downloaded DMG) in the system temp dir. async fn unmount(mut self) { let mount_path = mem::take(&mut self.mount_path); unmount_disk_image(&mount_path).await; } } impl Drop for MacOsUnmounter<'_> { fn drop(&mut self) { let mount_path = mem::take(&mut self.mount_path); // Safety net for early exits and cancellation; the happy path calls // `unmount`, which leaves the path empty. if mount_path.as_os_str().is_empty() { return; } self.background_executor .spawn(async move { unmount_disk_image(&mount_path).await }) .detach(); } } async fn unmount_disk_image(mount_path: &Path) { let unmount_output = new_command("hdiutil") .args(["detach", "-force"]) .arg(mount_path) .output() .await; match unmount_output { Ok(output) if output.status.success() => { log::info!("Successfully unmounted the disk image"); } Ok(output) => { log::error!( "Failed to unmount disk image: {:?}", String::from_utf8_lossy(&output.stderr) ); } Err(error) => { log::error!("Error while trying to unmount disk image: {:?}", error); } } } #[derive(Clone, Copy, Debug, RegisterSetting)] struct AutoUpdateSetting(bool); /// Whether or not to automatically check for updates. /// /// Default: true impl Settings for AutoUpdateSetting { fn from_settings(content: &settings::SettingsContent) -> Self { Self(content.auto_update.unwrap()) } } #[derive(Default)] struct GlobalAutoUpdate(Option<Entity<AutoUpdater>>); impl Global for GlobalAutoUpdate {} pub fn init(client: Arc<Client>, cx: &mut App) { cx.observe_new(|workspace: &mut Workspace, _window, _cx| { workspace.register_action(|_, action, window, cx| check(action, window, cx)); workspace.register_action(|_, action, _, cx| { view_release_notes(action, cx); }); }) .detach(); let version = release_channel::AppVersion::global(cx); let auto_updater = cx.new(|cx| { let updater = AutoUpdater::new(version, client, cx); let poll_for_updates = ReleaseChannel::try_global(cx) .map(|channel| channel.poll_for_updates()) .unwrap_or(false); if option_env!("ZED_UPDATE_EXPLANATION").is_none() && env::var("ZED_UPDATE_EXPLANATION").is_err() && poll_for_updates { let mut update_subscription = AutoUpdateSetting::get_global(cx) .0 .then(|| updater.start_polling(cx)); cx.observe_global::<SettingsStore>(move |updater: &mut AutoUpdater, cx| { if AutoUpdateSetting::get_global(cx).0 { if update_subscription.is_none() { update_subscription = Some(updater.start_polling(cx)) } } else { update_subscription.take(); } }) .detach(); } updater }); cx.set_global(GlobalAutoUpdate(Some(auto_updater))); } pub fn check(_: &Check, window: &mut Window, cx: &mut App) { if let Some(message) = option_env!("ZED_UPDATE_EXPLANATION") .map(ToOwned::to_owned) .or_else(|| env::var("ZED_UPDATE_EXPLANATION").ok()) { drop(window.prompt( gpui::PromptLevel::Info, "Zed was installed via a package manager.", Some(&message), &["OK"], cx, )); return; } if !ReleaseChannel::try_global(cx) .map(|channel| channel.poll_for_updates()) .unwrap_or(false) { return; } if let Some(updater) = AutoUpdater::get(cx) { updater.update(cx, |updater, cx| updater.poll(UpdateCheckType::Manual, cx)); } else { drop(window.prompt( gpui::PromptLevel::Info, "Could not check for updates", Some("Auto-updates disabled for non-bundled app."), &["OK"], cx, )); } } pub fn release_notes_url(cx: &mut App) -> Option<String> { let release_channel = ReleaseChannel::try_global(cx)?; let url = match release_channel { ReleaseChannel::Stable | ReleaseChannel::Preview => { let auto_updater = AutoUpdater::get(cx)?; let auto_updater = auto_updater.read(cx); let mut current_version = auto_updater.current_version.clone(); current_version.pre = semver::Prerelease::EMPTY; current_version.build = semver::BuildMetadata::EMPTY; let release_channel = release_channel.dev_name(); let path = format!("/releases/{release_channel}/{current_version}"); auto_updater.client.http_client().build_url(&path) } ReleaseChannel::Nightly => { "https://github.com/zed-industries/zed/commits/nightly/".to_string() } ReleaseChannel::Dev => "https://github.com/zed-industries/zed/commits/main/".to_string(), }; Some(url) } pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut App) -> Option<()> { let url = release_notes_url(cx)?; cx.open_url(&url); None } #[cfg(not(target_os = "windows"))] const INSTALLER_DIR_PREFIX: &str = "zed-auto-update"; #[cfg(not(target_os = "windows"))] struct InstallerDir(tempfile::TempDir); #[cfg(not(target_os = "windows"))] impl InstallerDir { async fn new() -> Result<Self> { Ok(Self( tempfile::Builder::new() .prefix(INSTALLER_DIR_PREFIX) .tempdir()?, )) } fn path(&self) -> &Path { self.0.path() } } #[cfg(target_os = "windows")] struct InstallerDir(PathBuf); #[cfg(target_os = "windows")] impl InstallerDir { async fn new() -> Result<Self> { let installer_dir = std::env::current_exe()? .parent() .context("No parent dir for Zed.exe")? .join("updates"); if smol::fs::metadata(&installer_dir).await.is_ok() { smol::fs::remove_dir_all(&installer_dir).await?; } smol::fs::create_dir(&installer_dir).await?; Ok(Self(installer_dir)) } fn path(&self) -> &Path { self.0.as_path() } } #[derive(Clone, Copy, Debug, PartialEq)] pub enum UpdateCheckType { Automatic, Manual, } impl UpdateCheckType { pub fn is_manual(self) -> bool { self == Self::Manual } } impl AutoUpdater { pub fn get(cx: &mut App) -> Option<Entity<Self>> { cx.default_global::<GlobalAutoUpdate>().0.clone() } fn new(current_version: Version, client: Arc<Client>, cx: &mut Context<Self>) -> Self { // On windows, executable files cannot be overwritten while they are // running, so we must wait to overwrite the application until quitting // or restarting. When quitting the app, we spawn the auto update helper // to finish the auto update process after Zed exits. When restarting // the app after an update, we use `set_restart_path` to run the auto // update helper instead of the app, so that it can overwrite the app // and then spawn the new binary. #[cfg(target_os = "windows")] let quit_subscription = Some(cx.on_app_quit(|_, _| finalize_auto_update_on_quit())); #[cfg(not(target_os = "windows"))] let quit_subscription = None; cx.on_app_restart(|this, _| { this.quit_subscription.take(); }) .detach(); Self { status: AutoUpdateStatus::Idle, current_version, client, pending_poll: None, quit_subscription, update_check_type: UpdateCheckType::Automatic, dismissed_status: None, } } pub fn start_polling(&self, cx: &mut Context<Self>) -> Task<Result<()>> { let poll_interval = ReleaseChannel::try_global(cx).map_or(POLL_INTERVAL, |channel| match channel { ReleaseChannel::Nightly => NIGHTLY_POLL_INTERVAL, _ => POLL_INTERVAL, }); cx.spawn(async move |this, cx| { if cfg!(target_os = "windows") { use util::ResultExt; cleanup_windows() .await .context("failed to cleanup old directories") .log_err(); } #[cfg(all(not(target_os = "windows"), not(test)))] cx.background_spawn(cleanup_stale_installer_dirs()).detach(); loop { this.update(cx, |this, cx| this.poll(UpdateCheckType::Automatic, cx))?; cx.background_executor().timer(poll_interval).await; } }) } pub fn update_check_type(&self) -> UpdateCheckType { self.update_check_type } pub fn poll(&mut self, check_type: UpdateCheckType, cx: &mut Context<Self>) { if check_type.is_manual() { self.dismissed_status = None; } if self.pending_poll.is_some() { if self.update_check_type == UpdateCheckType::Automatic { self.update_check_type = check_type; cx.notify(); } return; } self.update_check_type = check_type; cx.notify();