Repository: mandarons/icloud-drive-docker (python)

Issue:
[FEATURE] Allow retry_login_interval to set to -1 to retry login only once and exit
Thanks again for the great work. 

The way I use this container is that I run it once daily. Instead of setting the `sync_interval` to 86400, I set it to -1 so that it starts the container, runs a single sync, and then exits. This allows me to wrap it in some reporting and I don't have to worry about timing gradually shifting over time. 

However, every so often the iCloud needs a new authentication code. And when that fails, the container does not exit. I know when this happens because my phone gets the Apple authentication messages. So I'm wondering: can I get the container to exit after just a single retry? Perhaps by setting `retry_login_interval` to -1, not something I see in the documentation?

Pull request:
[FEATURE] Allow retry_login_interval to set to -1 to retry login only once and exit, closes #189

Patch:
diff --git a/src/sync.py b/src/sync.py
index b2eb99fb..66b500b4 100644
--- a/src/sync.py
+++ b/src/sync.py
@@ -100,6 +100,9 @@ def sync():
                     next_sync = (
                         datetime.datetime.now() + datetime.timedelta(seconds=sleep_for)
                     ).strftime("%c")
+                    if sleep_for < 0:
+                        LOGGER.info("retry_login_interval is < 0, exiting ...")
+                        break
                     LOGGER.info(f"Retrying login at {next_sync} ...")
                     last_send = notify.send(config, last_send)
                     sleep(sleep_for)


Test patch:
diff --git a/tests/test_sync.py b/tests/test_sync.py
index 5a79342a..05f044d1 100644
--- a/tests/test_sync.py
+++ b/tests/test_sync.py
@@ -12,7 +12,7 @@ from src import ENV_ICLOUD_PASSWORD_KEY, read_config, sync
 from tests import data
 
 
-class TestSyncDrive(unittest.TestCase):
+class TestSync(unittest.TestCase):
     """Tests class for sync.py file."""
 
     def remove_temp(self):
@@ -374,3 +374,46 @@ class TestSyncDrive(unittest.TestCase):
         )
         self.assertNotIn(".com.cn", actual.home_endpoint)
         self.assertNotIn(".com.cn", actual.setup_endpoint)
+
+    @patch("src.sync.sleep")
+    @patch(target="keyring.get_password", return_value=data.VALID_PASSWORD)
+    @patch(
+        target="src.config_parser.get_username", return_value=data.AUTHENTICATED_USER
+    )
+    @patch("icloudpy.ICloudPyService")
+    @patch("src.sync.read_config")
+    @patch("requests.post", side_effect=tests.mocked_usage_post)
+    def test_sync_negative_retry_login_interval(
+        self,
+        mock_usage_post,
+        mock_read_config,
+        mock_service,
+        mock_get_username,
+        mock_get_password,
+        mock_sleep,
+    ):
+        """Test for negative retry login interval."""
+        config = self.config.copy()
+        config["app"]["credentials"]["retry_login_interval"] = -1
+        mock_read_config.return_value = config
+        if ENV_ICLOUD_PASSWORD_KEY in os.environ:
+            del os.environ[ENV_ICLOUD_PASSWORD_KEY]
+
+        with self.assertLogs() as captured:
+            mock_get_username.return_value = data.REQUIRES_2FA_USER
+            mock_sleep.side_effect = [
+                None,
+            ]
+            sync.sync()
+        self.assertTrue(len(captured.records) > 1)
+        self.assertTrue(len([e for e in captured[1] if "2FA is required" in e]) > 0)
+        self.assertTrue(
+            len(
+                [
+                    e
+                    for e in captured[1]
+                    if "retry_login_interval is < 0, exiting ..." in e
+                ]
+            )
+            > 0
+        )