@@ -12,9 +12,7 @@ use crate::cache::{PatchInfo, UpdaterState};
1212use crate :: config:: { set_config, with_config, UpdateConfig } ;
1313use crate :: events:: { EventType , PatchEvent } ;
1414use crate :: logging:: init_logging;
15- use crate :: network:: {
16- download_to_path, patches_check_url, NetworkHooks , PatchCheckRequest , PatchCheckResponse ,
17- } ;
15+ use crate :: network:: { download_to_path, patches_check_url, NetworkHooks , PatchCheckRequest } ;
1816use crate :: updater_lock:: { with_updater_thread_lock, UpdaterLockState } ;
1917use crate :: yaml:: YamlConfig ;
2018
@@ -50,6 +48,13 @@ impl Display for UpdateStatus {
5048 }
5149}
5250
51+ /// Whether a patch is OK to install, and if not, why.
52+ pub enum ShouldInstallPatchCheckResult {
53+ PatchOkToInstall ,
54+ PatchKnownBad ,
55+ PatchAlreadyInstalled ,
56+ }
57+
5358/// Returned when a call to `init` is not successful. These indicate that the specific call to
5459/// `init` was not successful, but the library may still be in a valid state (e.g., if
5560/// `AlreadyInitialized` is returned, the library is still initialized). Callers can safely ignore
@@ -234,7 +239,8 @@ pub fn should_auto_update() -> anyhow::Result<bool> {
234239 with_config ( |config| Ok ( config. auto_update ) )
235240}
236241
237- fn check_for_update_internal ( ) -> anyhow:: Result < PatchCheckResponse > {
242+ /// Synchronously checks for an update and returns true if an update is available.
243+ pub fn check_for_update ( ) -> anyhow:: Result < bool > {
238244 let ( request, url, request_fn) = with_config ( |config| {
239245 // Get the required info to make the request.
240246 Ok ( (
@@ -246,12 +252,16 @@ fn check_for_update_internal() -> anyhow::Result<PatchCheckResponse> {
246252
247253 let response = request_fn ( & url, request) ?;
248254 debug ! ( "Patch check response: {:?}" , response) ;
249- Ok ( response)
250- }
251255
252- /// Synchronously checks for an update and returns true if an update is available.
253- pub fn check_for_update ( ) -> anyhow:: Result < bool > {
254- check_for_update_internal ( ) . map ( |res| res. patch_available )
256+ if let Some ( patch) = response. patch {
257+ match should_install_patch ( patch. number ) ? {
258+ ShouldInstallPatchCheckResult :: PatchOkToInstall => Ok ( true ) ,
259+ ShouldInstallPatchCheckResult :: PatchKnownBad => Ok ( false ) ,
260+ ShouldInstallPatchCheckResult :: PatchAlreadyInstalled => Ok ( false ) ,
261+ }
262+ } else {
263+ Ok ( false )
264+ }
255265}
256266
257267fn check_hash ( path : & Path , expected_string : & str ) -> anyhow:: Result < ( ) > {
@@ -370,23 +380,10 @@ fn update_internal(_: &UpdaterLockState) -> anyhow::Result<UpdateStatus> {
370380
371381 let patch = response. patch . ok_or ( UpdateError :: BadServerResponse ) ?;
372382
373- // Don't install a patch if it has previously failed to boot.
374- let is_known_bad_patch = with_state ( |state| Ok ( state. is_known_bad_patch ( patch. number ) ) ) ?;
375- if is_known_bad_patch {
376- info ! (
377- "Patch {} has previously failed to boot, skipping." ,
378- patch. number
379- ) ;
380- return Ok ( UpdateStatus :: UpdateIsBadPatch ) ;
381- }
382-
383- // If we already have the latest available patch downloaded, we don't need to download it again.
384- let next_boot_patch = with_mut_state ( |state| Ok ( state. next_boot_patch ( ) ) ) ?;
385- if let Some ( next_boot_patch) = next_boot_patch {
386- if next_boot_patch. number == patch. number {
387- info ! ( "Patch {} is already installed, skipping." , patch. number) ;
388- return Ok ( UpdateStatus :: NoUpdate ) ;
389- }
383+ match should_install_patch ( patch. number ) ? {
384+ ShouldInstallPatchCheckResult :: PatchOkToInstall => { }
385+ ShouldInstallPatchCheckResult :: PatchKnownBad => return Ok ( UpdateStatus :: UpdateIsBadPatch ) ,
386+ ShouldInstallPatchCheckResult :: PatchAlreadyInstalled => return Ok ( UpdateStatus :: NoUpdate ) ,
390387 }
391388
392389 let download_dir = PathBuf :: from ( & config. download_dir ) ;
@@ -437,6 +434,29 @@ fn update_internal(_: &UpdaterLockState) -> anyhow::Result<UpdateStatus> {
437434 } )
438435}
439436
437+ fn should_install_patch ( patch_number : usize ) -> Result < ShouldInstallPatchCheckResult > {
438+ // Don't install a patch if it has previously failed to boot.
439+ let is_known_bad_patch = with_state ( |state| Ok ( state. is_known_bad_patch ( patch_number) ) ) ?;
440+ if is_known_bad_patch {
441+ info ! (
442+ "Patch {} has previously failed to boot, skipping." ,
443+ patch_number
444+ ) ;
445+ return Ok ( ShouldInstallPatchCheckResult :: PatchKnownBad ) ;
446+ }
447+
448+ // If we already have the latest available patch downloaded, we don't need to download it again.
449+ let next_boot_patch = with_mut_state ( |state| Ok ( state. next_boot_patch ( ) ) ) ?;
450+ if let Some ( next_boot_patch) = next_boot_patch {
451+ if next_boot_patch. number == patch_number {
452+ info ! ( "Patch {} is already installed, skipping." , patch_number) ;
453+ return Ok ( ShouldInstallPatchCheckResult :: PatchAlreadyInstalled ) ;
454+ }
455+ }
456+
457+ Ok ( ShouldInstallPatchCheckResult :: PatchOkToInstall )
458+ }
459+
440460/// Synchronously checks for an update and downloads and installs it if available.
441461pub fn update ( ) -> anyhow:: Result < UpdateStatus > {
442462 with_updater_thread_lock ( update_internal)
@@ -1428,3 +1448,86 @@ mod rollback_tests {
14281448 Ok ( ( ) )
14291449 }
14301450}
1451+
1452+ #[ cfg( test) ]
1453+ mod check_for_update_tests {
1454+ use anyhow:: Result ;
1455+ use serial_test:: serial;
1456+ use tempdir:: TempDir ;
1457+
1458+ use crate :: {
1459+ network:: PatchCheckResponse , report_launch_failure, report_launch_start,
1460+ test_utils:: install_fake_patch, updater:: tests:: init_for_testing,
1461+ } ;
1462+
1463+ use super :: Patch ;
1464+
1465+ fn mock_server ( available_patch_number : usize ) -> mockito:: ServerGuard {
1466+ let mut server = mockito:: Server :: new ( ) ;
1467+ let check_response = PatchCheckResponse {
1468+ patch_available : true ,
1469+ patch : Some ( Patch {
1470+ number : available_patch_number,
1471+ hash : "#" . to_string ( ) ,
1472+ download_url : "download_url" . to_string ( ) ,
1473+ hash_signature : None ,
1474+ } ) ,
1475+ rolled_back_patch_numbers : None ,
1476+ } ;
1477+ let check_response_body = serde_json:: to_string ( & check_response) . unwrap ( ) ;
1478+ let _ = server
1479+ . mock ( "POST" , "/api/v1/patches/check" )
1480+ . with_status ( 200 )
1481+ . with_body ( check_response_body)
1482+ . create ( ) ;
1483+ server
1484+ }
1485+
1486+ #[ serial]
1487+ #[ test]
1488+ fn returns_false_if_patch_is_already_installed ( ) -> Result < ( ) > {
1489+ let patch_number = 1 ;
1490+ let server = mock_server ( patch_number) ;
1491+ let tmp_dir = TempDir :: new ( "example" ) . unwrap ( ) ;
1492+ init_for_testing ( & tmp_dir, Some ( & server. url ( ) ) ) ;
1493+
1494+ install_fake_patch ( patch_number) ?;
1495+
1496+ let is_update_available = crate :: check_for_update ( ) ?;
1497+ assert ! ( !is_update_available) ;
1498+
1499+ Ok ( ( ) )
1500+ }
1501+
1502+ #[ serial]
1503+ #[ test]
1504+ fn returns_false_if_patch_is_known_bad ( ) -> Result < ( ) > {
1505+ let patch_number = 1 ;
1506+ let server = mock_server ( patch_number) ;
1507+ let tmp_dir = TempDir :: new ( "example" ) . unwrap ( ) ;
1508+ init_for_testing ( & tmp_dir, Some ( & server. url ( ) ) ) ;
1509+
1510+ install_fake_patch ( patch_number) ?;
1511+ report_launch_start ( ) ?;
1512+ report_launch_failure ( ) ?;
1513+
1514+ let is_update_available = crate :: check_for_update ( ) ?;
1515+ assert ! ( !is_update_available) ;
1516+
1517+ Ok ( ( ) )
1518+ }
1519+
1520+ #[ serial]
1521+ #[ test]
1522+ fn returns_true_if_patch_has_no_issues ( ) -> Result < ( ) > {
1523+ let patch_number = 1 ;
1524+ let server = mock_server ( patch_number) ;
1525+ let tmp_dir = TempDir :: new ( "example" ) . unwrap ( ) ;
1526+ init_for_testing ( & tmp_dir, Some ( & server. url ( ) ) ) ;
1527+
1528+ let is_update_available = crate :: check_for_update ( ) ?;
1529+ assert ! ( is_update_available) ;
1530+
1531+ Ok ( ( ) )
1532+ }
1533+ }
0 commit comments