DownloadManager cleanups

This commit is contained in:
Thomas Farstrike
2025-12-24 14:23:10 +01:00
parent 13747b8a65
commit b62e115613
4 changed files with 100 additions and 34 deletions
@@ -64,7 +64,10 @@ class AppStore(Activity):
response = await DownloadManager.download_url(json_url)
except Exception as e:
print(f"Failed to download app index: {e}")
self.please_wait_label.set_text(f"Could not download app index from\n{json_url}\nError: {e}")
if DownloadManager.is_network_error(e):
self.please_wait_label.set_text(f"Network error - check your WiFi connection\nand try again.")
else:
self.please_wait_label.set_text(f"Could not download app index from\n{json_url}\nError: {e}")
return
print(f"Got response text: {response[0:20]}")
try:
@@ -203,6 +206,8 @@ class AppStore(Activity):
response = await DownloadManager.download_url(details_url)
except Exception as e:
print(f"Could not download app details from {details_url}: {e}")
if DownloadManager.is_network_error(e):
print("Network error while fetching app details")
return
print(f"Got response text: {response[0:20]}")
try:
@@ -494,7 +499,10 @@ class AppDetail(Activity):
self.progress_bar.set_value(90, True)
except Exception as e:
print(f"Download failed with exception: {e}")
self.install_label.set_text(f"Download failed")
if DownloadManager.is_network_error(e):
self.install_label.set_text(f"Network error - check WiFi")
else:
self.install_label.set_text(f"Download failed: {str(e)[:30]}")
self.install_button.remove_state(lv.STATE.DISABLED)
self.progress_bar.add_flag(lv.obj.FLAG.HIDDEN)
self.progress_bar.set_value(0, False)
@@ -212,7 +212,7 @@ class OSUpdate(Activity):
except Exception as e:
print(f"show_update_info got exception: {e}")
# Check if this is a network connectivity error
if self.update_downloader._is_network_error(e):
if DownloadManager.is_network_error(e):
# Network not available - wait for it to come back
print("OSUpdate: Network error while checking for updates, waiting for WiFi")
self.set_state(UpdateState.WAITING_WIFI)
@@ -461,35 +461,6 @@ class UpdateDownloader:
print("UpdateDownloader: Partition module not available, will simulate")
self.simulate = True
def _is_network_error(self, exception):
"""Check if exception is a network connectivity error that should trigger pause.
Args:
exception: Exception to check
Returns:
bool: True if this is a recoverable network error
"""
error_str = str(exception).lower()
error_repr = repr(exception).lower()
# Check for common network error codes and messages
# -113 = ECONNABORTED (connection aborted)
# -104 = ECONNRESET (connection reset by peer)
# -110 = ETIMEDOUT (connection timed out)
# -118 = EHOSTUNREACH (no route to host)
# -202 = DNS/connection error (network not ready)
network_indicators = [
'-113', '-104', '-110', '-118', '-202', # Error codes
'econnaborted', 'econnreset', 'etimedout', 'ehostunreach', # Error names
'connection reset', 'connection aborted', # Error messages
'broken pipe', 'network unreachable', 'host unreachable',
'failed to download chunk' # From download_manager OSError(-110)
]
return any(indicator in error_str or indicator in error_repr
for indicator in network_indicators)
def _setup_partition(self):
"""Initialize the OTA partition for writing."""
if not self.simulate and self._current_partition is None:
@@ -678,7 +649,7 @@ class UpdateDownloader:
result['bytes_written'] = self.bytes_written_so_far
result['total_size'] = self.total_size_expected
# Check if this is a network error that should trigger pause
elif self._is_network_error(e):
elif DownloadManager.is_network_error(e):
print(f"UpdateDownloader: Network error ({e}), pausing download")
# Clear buffer - we'll re-download this data on resume
@@ -11,7 +11,7 @@ adc_pin = None
_cached_raw_adc = None
_last_read_time = 0
CACHE_DURATION_ADC1_MS = 30000 # 30 seconds (cheaper: no WiFi interference)
CACHE_DURATION_ADC2_MS = 300000 # 300 seconds (expensive: requires WiFi disable)
CACHE_DURATION_ADC2_MS = 600000 # 600 seconds (expensive: requires WiFi disable)
#CACHE_DURATION_ADC2_MS = CACHE_DURATION_ADC1_MS # trigger frequent disconnections for debugging OSUpdate resume
def _is_adc2_pin(pin):
@@ -14,6 +14,11 @@ Features:
- Progress tracking with 2-decimal precision
- Download speed reporting
- Resume support via Range headers
- Network error detection utilities
Utility Functions:
is_network_error(exception) - Check if error is recoverable network error
get_resume_position(outfile) - Get file size for resume support
Example:
from mpos import DownloadManager
@@ -44,6 +49,19 @@ Example:
"https://example.com/stream",
chunk_callback=process_chunk
)
# Error handling with retry
try:
await DownloadManager.download_url(url, outfile="/sdcard/file.bin")
except Exception as e:
if DownloadManager.is_network_error(e):
# Wait and retry with resume
await asyncio.sleep(2)
resume_from = DownloadManager.get_resume_position("/sdcard/file.bin")
headers = {'Range': f'bytes={resume_from}-'} if resume_from > 0 else None
await DownloadManager.download_url(url, outfile="/sdcard/file.bin", headers=headers)
else:
raise # Fatal error
"""
# Constants
@@ -174,6 +192,75 @@ async def close_session():
_session_lock.release()
def is_network_error(exception):
"""Check if exception is a recoverable network error.
Recognizes common network error codes and messages that indicate
temporary connectivity issues that can be retried.
Args:
exception: Exception to check
Returns:
bool: True if this is a network error that can be retried
Example:
try:
await DownloadManager.download_url(url)
except Exception as e:
if DownloadManager.is_network_error(e):
# Retry or pause
await asyncio.sleep(2)
# retry...
else:
# Fatal error
raise
"""
error_str = str(exception).lower()
error_repr = repr(exception).lower()
# Common network error codes and messages
# -113 = ECONNABORTED (connection aborted)
# -104 = ECONNRESET (connection reset by peer)
# -110 = ETIMEDOUT (connection timed out)
# -118 = EHOSTUNREACH (no route to host)
# -202 = DNS/connection error (network not ready)
network_indicators = [
'-113', '-104', '-110', '-118', '-202', # Error codes
'econnaborted', 'econnreset', 'etimedout', 'ehostunreach', # Error names
'connection reset', 'connection aborted', # Error messages
'broken pipe', 'network unreachable', 'host unreachable',
'failed to download chunk' # From download_manager OSError(-110)
]
return any(indicator in error_str or indicator in error_repr
for indicator in network_indicators)
def get_resume_position(outfile):
"""Get the current size of a partially downloaded file.
Useful for implementing resume functionality with Range headers.
Args:
outfile: Path to file
Returns:
int: File size in bytes, or 0 if file doesn't exist
Example:
resume_from = DownloadManager.get_resume_position("/sdcard/file.bin")
if resume_from > 0:
headers = {'Range': f'bytes={resume_from}-'}
await DownloadManager.download_url(url, outfile=outfile, headers=headers)
"""
try:
import os
return os.stat(outfile)[6] # st_size
except OSError:
return 0
async def download_url(url, outfile=None, total_size=None,
progress_callback=None, chunk_callback=None, headers=None,
speed_callback=None):