11use anyhow:: { Context as _, Result } ;
22use client:: Client ;
3- use db:: RELEASE_CHANNEL ;
43use db:: kvp:: KEY_VALUE_STORE ;
54use gpui:: {
65 App , AppContext as _, AsyncApp , BackgroundExecutor , Context , Entity , Global , SemanticVersion ,
@@ -57,7 +56,7 @@ pub struct AssetQuery<'a> {
5756 is_staff : Option < bool > ,
5857}
5958
60- #[ derive( Clone ) ]
59+ #[ derive( Clone , Debug ) ]
6160pub enum AutoUpdateStatus {
6261 Idle ,
6362 Checking ,
@@ -67,6 +66,29 @@ pub enum AutoUpdateStatus {
6766 Errored { error : Arc < anyhow:: Error > } ,
6867}
6968
69+ impl PartialEq for AutoUpdateStatus {
70+ fn eq ( & self , other : & Self ) -> bool {
71+ match ( self , other) {
72+ ( AutoUpdateStatus :: Idle , AutoUpdateStatus :: Idle ) => true ,
73+ ( AutoUpdateStatus :: Checking , AutoUpdateStatus :: Checking ) => true ,
74+ (
75+ AutoUpdateStatus :: Downloading { version : v1 } ,
76+ AutoUpdateStatus :: Downloading { version : v2 } ,
77+ ) => v1 == v2,
78+ (
79+ AutoUpdateStatus :: Installing { version : v1 } ,
80+ AutoUpdateStatus :: Installing { version : v2 } ,
81+ ) => v1 == v2,
82+ (
83+ AutoUpdateStatus :: Updated { version : v1 } ,
84+ AutoUpdateStatus :: Updated { version : v2 } ,
85+ ) => v1 == v2,
86+ ( AutoUpdateStatus :: Errored { .. } , AutoUpdateStatus :: Errored { .. } ) => true , // Compare errors by existence, not content
87+ _ => false ,
88+ }
89+ }
90+ }
91+
7092impl AutoUpdateStatus {
7193 pub fn is_updated ( & self ) -> bool {
7294 matches ! ( self , Self :: Updated { .. } )
@@ -76,12 +98,12 @@ impl AutoUpdateStatus {
7698pub struct AutoUpdater {
7799 status : AutoUpdateStatus ,
78100 current_version : SemanticVersion ,
79- http_client : Arc < HttpClientWithUrl > ,
101+ client : Arc < Client > ,
80102 pending_poll : Option < Task < Option < ( ) > > > ,
81103 quit_subscription : Option < gpui:: Subscription > ,
82104}
83105
84- #[ derive( Deserialize , Clone , Debug ) ]
106+ #[ derive( Deserialize , Serialize , Clone , Debug ) ]
85107pub struct ReleaseAsset {
86108 pub version : String ,
87109 pub url : String ,
@@ -138,7 +160,7 @@ struct GlobalAutoUpdate(Option<Entity<AutoUpdater>>);
138160
139161impl Global for GlobalAutoUpdate { }
140162
141- pub fn init ( http_client : Arc < HttpClientWithUrl > , cx : & mut App ) {
163+ pub fn init ( client : Arc < Client > , cx : & mut App ) {
142164 AutoUpdateSetting :: register ( cx) ;
143165
144166 cx. observe_new ( |workspace : & mut Workspace , _window, _cx| {
@@ -152,7 +174,7 @@ pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut App) {
152174
153175 let version = release_channel:: AppVersion :: global ( cx) ;
154176 let auto_updater = cx. new ( |cx| {
155- let updater = AutoUpdater :: new ( version, http_client , cx) ;
177+ let updater = AutoUpdater :: new ( version, client , cx) ;
156178
157179 let poll_for_updates = ReleaseChannel :: try_global ( cx)
158180 . map ( |channel| channel. poll_for_updates ( ) )
@@ -236,7 +258,7 @@ pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut App) -> Option<()> {
236258 let current_version = auto_updater. current_version ;
237259 let release_channel = release_channel. dev_name ( ) ;
238260 let path = format ! ( "/releases/{release_channel}/{current_version}" ) ;
239- let url = & auto_updater. http_client . build_url ( & path) ;
261+ let url = & auto_updater. client . http_client ( ) . build_url ( & path) ;
240262 cx. open_url ( url) ;
241263 }
242264 ReleaseChannel :: Nightly => {
@@ -299,11 +321,7 @@ impl AutoUpdater {
299321 cx. default_global :: < GlobalAutoUpdate > ( ) . 0 . clone ( )
300322 }
301323
302- fn new (
303- current_version : SemanticVersion ,
304- http_client : Arc < HttpClientWithUrl > ,
305- cx : & mut Context < Self > ,
306- ) -> Self {
324+ fn new ( current_version : SemanticVersion , client : Arc < Client > , cx : & mut Context < Self > ) -> Self {
307325 // On windows, executable files cannot be overwritten while they are
308326 // running, so we must wait to overwrite the application until quitting
309327 // or restarting. When quitting the app, we spawn the auto update helper
@@ -324,13 +342,14 @@ impl AutoUpdater {
324342 Self {
325343 status : AutoUpdateStatus :: Idle ,
326344 current_version,
327- http_client ,
345+ client ,
328346 pending_poll : None ,
329347 quit_subscription,
330348 }
331349 }
332350
333351 pub fn start_polling ( & self , cx : & mut Context < Self > ) -> Task < Result < ( ) > > {
352+ dbg ! ( "start polling" ) ;
334353 cx. spawn ( async move |this, cx| {
335354 #[ cfg( target_os = "windows" ) ]
336355 {
@@ -351,6 +370,7 @@ impl AutoUpdater {
351370
352371 pub fn poll ( & mut self , check_type : UpdateCheckType , cx : & mut Context < Self > ) {
353372 if self . pending_poll . is_some ( ) {
373+ dbg ! ( "return" ) ;
354374 return ;
355375 }
356376
@@ -435,7 +455,7 @@ impl AutoUpdater {
435455 let version_path = platform_dir. join ( format ! ( "{}.gz" , release. version) ) ;
436456 smol:: fs:: create_dir_all ( & platform_dir) . await . ok ( ) ;
437457
438- let client = this. read_with ( cx, |this, _| this. http_client . clone ( ) ) ?;
458+ let client = this. read_with ( cx, |this, _| this. client . http_client ( ) ) ?;
439459
440460 if smol:: fs:: metadata ( & version_path) . await . is_err ( ) {
441461 log:: info!(
@@ -479,10 +499,10 @@ impl AutoUpdater {
479499 arch : & str ,
480500 cx : & mut AsyncApp ,
481501 ) -> Result < ReleaseAsset > {
482- let client = this. read_with ( cx, |this, _| this. http_client . clone ( ) ) ?;
502+ let client = this. read_with ( cx, |this, _| this. client . clone ( ) ) ?;
483503
484504 let ( system_id, metrics_id, is_staff) = cx. update ( |cx| {
485- let telemetry = Client :: global ( cx ) . telemetry ( ) . clone ( ) ;
505+ let telemetry = client . telemetry ( ) . clone ( ) ;
486506 if telemetry. metrics_enabled ( ) {
487507 (
488508 telemetry. system_id ( ) ,
@@ -499,9 +519,10 @@ impl AutoUpdater {
499519 } else {
500520 "latest" . to_string ( )
501521 } ;
522+ let http_client = client. http_client ( ) ;
502523
503524 let path = format ! ( "/releases/{}/{}/asset" , release_channel. dev_name( ) , version, ) ;
504- let url = client . build_zed_cloud_url_with_query (
525+ let url = http_client . build_zed_cloud_url_with_query (
505526 & path,
506527 AssetQuery {
507528 os,
@@ -513,7 +534,9 @@ impl AutoUpdater {
513534 } ,
514535 ) ?;
515536
516- let mut response = client. get ( url. as_str ( ) , Default :: default ( ) , true ) . await ?;
537+ let mut response = http_client
538+ . get ( url. as_str ( ) , Default :: default ( ) , true )
539+ . await ?;
517540 let mut body = Vec :: new ( ) ;
518541 response. body_mut ( ) . read_to_end ( & mut body) . await ?;
519542
@@ -535,7 +558,7 @@ impl AutoUpdater {
535558 let ( client, installed_version, previous_status, release_channel) =
536559 this. read_with ( cx, |this, cx| {
537560 (
538- this. http_client . clone ( ) ,
561+ this. client . http_client ( ) ,
539562 this. current_version ,
540563 this. status . clone ( ) ,
541564 ReleaseChannel :: try_global ( cx) . unwrap_or ( ReleaseChannel :: Stable ) ,
@@ -555,7 +578,7 @@ impl AutoUpdater {
555578 let fetched_version = fetched_release_data. clone ( ) . version ;
556579 let app_commit_sha = cx. update ( |cx| AppCommitSha :: try_global ( cx) . map ( |sha| sha. full ( ) ) ) ;
557580 let newer_version = Self :: check_if_fetched_version_is_newer (
558- * RELEASE_CHANNEL ,
581+ release_channel ,
559582 app_commit_sha,
560583 installed_version,
561584 fetched_version,
@@ -955,8 +978,12 @@ pub async fn finalize_auto_update_on_quit() {
955978
956979#[ cfg( test) ]
957980mod tests {
981+ use client:: Client ;
982+ use clock:: FakeSystemClock ;
958983 use gpui:: TestAppContext ;
984+ use http_client:: FakeHttpClient ;
959985 use settings:: default_settings;
986+ use std:: sync:: Arc ;
960987
961988 use super :: * ;
962989
@@ -976,6 +1003,84 @@ mod tests {
9761003 } ) ;
9771004 }
9781005
1006+ #[ gpui:: test]
1007+ async fn test_auto_update_end_to_end ( cx : & mut TestAppContext ) {
1008+ cx. update ( |cx| {
1009+ settings:: init ( cx) ;
1010+
1011+ let current_version = SemanticVersion :: new ( 1 , 0 , 0 ) ;
1012+ release_channel:: init_test ( current_version, ReleaseChannel :: Stable , cx) ;
1013+
1014+ client:: init_settings ( cx) ;
1015+ let clock = Arc :: new ( FakeSystemClock :: new ( ) ) ;
1016+ let fake_client_http = FakeHttpClient :: with_404_response ( ) ;
1017+ let client = Client :: new ( clock, fake_client_http, cx) ;
1018+ crate :: init ( client, cx) ;
1019+ } ) ;
1020+
1021+ let auto_updater = cx. update ( |cx| AutoUpdater :: get ( cx) . expect ( "auto updater should exist" ) ) ;
1022+
1023+ cx. background_executor . run_until_parked ( ) ;
1024+
1025+ // Verify initial state
1026+ auto_updater. read_with ( cx, |updater, _| {
1027+ assert_eq ! ( updater. status( ) , AutoUpdateStatus :: Idle ) ;
1028+ assert_eq ! ( updater. current_version( ) , SemanticVersion :: new( 1 , 0 , 0 ) ) ;
1029+ } ) ;
1030+
1031+ // Trigger manual update check
1032+ auto_updater. update ( cx, |updater, cx| {
1033+ updater. poll ( UpdateCheckType :: Manual , cx) ;
1034+ } ) ;
1035+
1036+ // Let the initial state change propagate
1037+
1038+ // Verify it transitions to Checking state
1039+ let status = auto_updater. read_with ( cx, |updater, _| updater. status ( ) ) ;
1040+
1041+ assert_eq ! ( status, AutoUpdateStatus :: Checking ) ;
1042+
1043+ // Let the async update process continue
1044+ cx. background_executor . run_until_parked ( ) ;
1045+
1046+ // The update should progress through states and eventually either:
1047+ // - Reach AutoUpdateStatus::Updated if mocking is complete
1048+ // - Reach AutoUpdateStatus::Errored when download/install fails (expected in test)
1049+ let final_status = auto_updater. read_with ( cx, |updater, _| updater. status ( ) ) ;
1050+
1051+ match final_status {
1052+ AutoUpdateStatus :: Updated { version } => {
1053+ // If somehow the update completed successfully
1054+ match version {
1055+ VersionCheckType :: Semantic ( v) => {
1056+ assert_eq ! ( v, SemanticVersion :: new( 1 , 0 , 1 ) ) ;
1057+ }
1058+ _ => panic ! ( "Expected semantic version for stable release" ) ,
1059+ }
1060+ }
1061+ AutoUpdateStatus :: Errored { .. } => {
1062+ // This is expected because we can't actually download/install in tests
1063+ // The important thing is that it attempted the update process
1064+ // and went through the checking phase successfully
1065+ }
1066+ AutoUpdateStatus :: Downloading { version } => {
1067+ // The update got stuck in downloading state due to our 404 mock
1068+ match version {
1069+ VersionCheckType :: Semantic ( v) => {
1070+ assert_eq ! ( v, SemanticVersion :: new( 1 , 0 , 1 ) ) ;
1071+ }
1072+ _ => panic ! ( "Expected semantic version for stable release" ) ,
1073+ }
1074+ }
1075+ other => {
1076+ panic ! (
1077+ "Expected Updated, Errored, or Downloading state after update attempt, got: {:?}" ,
1078+ other
1079+ ) ;
1080+ }
1081+ }
1082+ }
1083+
9791084 #[ test]
9801085 fn test_stable_does_not_update_when_fetched_version_is_not_higher ( ) {
9811086 let release_channel = ReleaseChannel :: Stable ;
0 commit comments