diff --git a/pyicloud/services/findmyiphone.py b/pyicloud/services/findmyiphone.py index e108393e..8b113ba5 100644 --- a/pyicloud/services/findmyiphone.py +++ b/pyicloud/services/findmyiphone.py @@ -103,29 +103,37 @@ def _refresh_client_with_reauth(self, locate: bool, retry: bool = False) -> None def _initialize_devices(self, locate: bool) -> None: """Initializes the devices for the FindMyiPhoneServiceManager.""" - # If family sharing is enabled, we may need to poll until all devices are ready - # This is indicated by the deviceFetchStatus being "LOADING" + # If family sharing is enabled, we may need to poll until all devices are ready. + # This is indicated by the deviceFetchStatus being "LOADING". + # Some members (e.g. Macs with location sharing disabled) remain permanently + # LOADING and will never resolve. Track which members are LOADING between + # retries and stop as soon as there is no progress, rather than always + # exhausting _MAX_REFRESH_RETRIES for stuck members. retries: int = 0 + prev_loading_keys: set[str] = set() while ( self._with_family and self._user_info and self._user_info.get("hasMembers", False) + and retries < _MAX_REFRESH_RETRIES ): - needs_refresh: bool = False - for user in self._user_info.get("membersInfo", {}).values(): - if user.get("deviceFetchStatus") == "LOADING": - needs_refresh = True - break - - if needs_refresh: - time.sleep(0.1) - self._refresh_client(locate=locate) - retries += 1 - if retries >= _MAX_REFRESH_RETRIES: - _LOGGER.debug("Max retries reached when fetching family devices") - break - else: - break + loading_keys = { + k + for k, v in self._user_info.get("membersInfo", {}).items() + if v.get("deviceFetchStatus") == "LOADING" + } + if not loading_keys: + break # all family members ready + if loading_keys == prev_loading_keys: + _LOGGER.debug( + "No progress on LOADING family members after retry %d, stopping", + retries, + ) + break # no change since last retry — give up on permanently stuck members + prev_loading_keys = loading_keys + time.sleep(0.1) + self._refresh_client(locate=locate) + retries += 1 if not self._devices: raise PyiCloudNoDevicesException() diff --git a/tests/services/test_findmyiphone.py b/tests/services/test_findmyiphone.py index 686484ec..d5c56f6f 100644 --- a/tests/services/test_findmyiphone.py +++ b/tests/services/test_findmyiphone.py @@ -691,7 +691,12 @@ def test_refresh_client_with_reauth_with_loading_to_done( def test_refresh_client_with_reauth_with_loading_no_complete( pyicloud_service_working: PyiCloudService, ) -> None: - """Test refresh_client_with_reauth calls _refresh_client if the members are loading.""" + """Test that the retry loop stops as soon as no LOADING members make progress. + + member2 resolves after the first retry (progress made → continue). + member1 remains LOADING after the second retry (no change → stop early). + Total refresh calls: 1 initial + 2 loop iterations = 3. + """ with patch( "pyicloud.services.findmyiphone.FindMyiPhoneServiceManager._refresh_client_with_reauth", return_value=None, @@ -753,69 +758,9 @@ def test_refresh_client_with_reauth_with_loading_no_complete( "deviceFetchStatus": "DONE", }, }, - True, - { - "member1": { - "firstName": "Member1", - "lastName": "One", - "appleId": "member1@example.com", - "deviceFetchStatus": "LOADING", - }, - "member2": { - "firstName": "Member2", - "lastName": "Two", - "appleId": "member2@example.com", - "deviceFetchStatus": "DONE", - }, - }, - True, - { - "member1": { - "firstName": "Member1", - "lastName": "One", - "appleId": "member1@example.com", - "deviceFetchStatus": "LOADING", - }, - "member2": { - "firstName": "Member2", - "lastName": "Two", - "appleId": "member2@example.com", - "deviceFetchStatus": "DONE", - }, - }, - True, - { - "member1": { - "firstName": "Member1", - "lastName": "One", - "appleId": "member1@example.com", - "deviceFetchStatus": "DONE", - }, - "member2": { - "firstName": "Member2", - "lastName": "Two", - "appleId": "member2@example.com", - "deviceFetchStatus": "DONE", - }, - }, - True, - { - "member1": { - "firstName": "Member1", - "lastName": "One", - "appleId": "member1@example.com", - "deviceFetchStatus": "LOADING", - }, - "member2": { - "firstName": "Member2", - "lastName": "Two", - "appleId": "member2@example.com", - "deviceFetchStatus": "DONE", - }, - }, ] manager._refresh_client_with_reauth(locate=True) - assert mock_refresh.call_count == 6 + assert mock_refresh.call_count == 3 mock_refresh.assert_called_with(locate=True)