diff --git a/CHANGELOG.md b/CHANGELOG.md index d03f979e..fe84e8ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +0.5.1 +===== +- OSUpdate app: pause download when wifi is lost, resume when reconnected +- Fri3d Camp 2024 Badge: workaround ADC2+WiFi conflict by disconnecting WiFi to measure battery level + 0.5.0 ===== - ESP32: one build to rule them all; instead of 2 builds per supported board, there is now one single build that identifies and initializes the board at runtime! diff --git a/internal_filesystem/builtin/apps/com.micropythonos.osupdate/assets/osupdate.py b/internal_filesystem/builtin/apps/com.micropythonos.osupdate/assets/osupdate.py index 15f2cc52..3af8c3d8 100644 --- a/internal_filesystem/builtin/apps/com.micropythonos.osupdate/assets/osupdate.py +++ b/internal_filesystem/builtin/apps/com.micropythonos.osupdate/assets/osupdate.py @@ -576,9 +576,13 @@ class UpdateDownloader: if self._is_network_error(e): print(f"UpdateDownloader: Network error ({e}), pausing download") self.is_paused = True - self.bytes_written_so_far = result.get('bytes_written', self.bytes_written_so_far) + # Only update bytes_written_so_far if we actually wrote bytes in this attempt + # Otherwise preserve the existing state (important for resume failures) + if result.get('bytes_written', 0) > 0: + self.bytes_written_so_far = result['bytes_written'] result['paused'] = True result['bytes_written'] = self.bytes_written_so_far + result['total_size'] = self.total_size_expected # Preserve total size for UI else: # Non-network error result['error'] = str(e) diff --git a/tests/test_osupdate.py b/tests/test_osupdate.py index e5a888ba..16e52fd2 100644 --- a/tests/test_osupdate.py +++ b/tests/test_osupdate.py @@ -460,4 +460,27 @@ class TestUpdateDownloader(unittest.TestCase): self.assertIn('Range', last_request['headers']) self.assertEqual(last_request['headers']['Range'], 'bytes=8192-') + def test_resume_failure_preserves_state(self): + """Test that resume failures preserve download state for retry.""" + # Simulate partial download state + self.downloader.bytes_written_so_far = 245760 # 60 chunks already downloaded + self.downloader.total_size_expected = 3391488 + + # Resume attempt fails immediately with EHOSTUNREACH (network not ready) + self.mock_requests.set_exception(OSError(-118, "EHOSTUNREACH")) + + result = self.downloader.download_and_install( + "http://example.com/update.bin" + ) + + # Should pause, not fail + self.assertFalse(result['success']) + self.assertTrue(result['paused']) + self.assertIsNone(result['error']) + + # Critical: Must preserve progress for next retry + self.assertEqual(result['bytes_written'], 245760, "Must preserve bytes_written") + self.assertEqual(result['total_size'], 3391488, "Must preserve total_size") + self.assertEqual(self.downloader.bytes_written_so_far, 245760, "Must preserve internal state") +